[{"data":1,"prerenderedAt":483},["ShallowReactive",2],{"developers:\u002Fdevelopers\u002Fsecurity":3},{"doc":4,"lang":316,"nav":327,"alternatePath":427,"policy":428,"specVersion":472,"reference":7,"layout":473,"toc":474,"prev":480,"next":481,"operations":482,"overview":7,"heroExample":7},{"id":5,"title":6,"apiTag":7,"body":8,"description":313,"draft":314,"extension":315,"lang":316,"meta":317,"navTitle":7,"navigation":318,"order":319,"path":320,"rawbody":321,"section":322,"seo":323,"stem":324,"translationKey":325,"version":7,"__hash__":326},"developers_en\u002Fdevelopers\u002Fsecurity.md","Security and personal data",null,{"type":9,"value":10,"toc":303},"minimark",[11,21,26,109,113,127,138,144,148,212,216,219,281,285],[12,13,14,15,20],"p",{},"This page is for whoever reviews the security of an integration, on the customer's side or on yours. It sums up how Totem protects keys and data, and what to do with the personal data you copy. ",[16,17,19],"a",{"href":18},"\u002Fdevelopers\u002Fauthentication","Authentication"," has the details of each control.",[22,23,25],"h2",{"id":24},"keys","Keys",[27,28,29,42,48,59,69,81,87,98],"ul",{},[30,31,32,36,37,41],"li",{},[33,34,35],"strong",{},"A key is a long random secret."," It starts with ",[38,39,40],"code",{},"tsk_live_",", then a lookup id, then about 256 bits of random secret and a checksum. The checksum lets Totem refuse a mistyped key before looking it up.",[30,43,44,47],{},[33,45,46],{},"Totem never stores the key itself."," It keeps a one-way digest of the secret, computed with HMAC-SHA-256 and a separate secret that is kept outside Totem's database, so a copy of the database is not enough to use a key. Totem shows the key once, when it is created, and its logs name a key by its lookup id, never by its secret.",[30,49,50,53,54,58],{},[33,51,52],{},"Keys do not expire."," A key works until you roll it or revoke it. Roll keys on a schedule that suits you, for example every 90 days, and whenever someone who had access leaves; ",[16,55,57],{"href":56},"\u002Fdevelopers\u002Ftesting#rehearse-a-key-rotation","Testing and go-live"," shows how to rehearse it.",[30,60,61,64,65,68],{},[33,62,63],{},"Keys are managed only in Settings."," Creating, rolling and revoking keys happen in ",[33,66,67],{},"Settings → Developers",", never through the API. Creating or rolling a key, adding a team and changing the IP allowlist ask the administrator to sign in again if they have not done so recently, and the administrators who manage the integration, and those of every team it covers, are told about every change, in Totem and by email.",[30,70,71,74,75,80],{},[33,72,73],{},"Revoking is fast."," A revoked key stops working within ",[76,77],"api-const",{"name":78,"divide":79},"KEY_CACHE_TTL_MS","1000"," seconds, and revoking never needs a new sign-in, so it can be done at once during an incident.",[30,82,83,86],{},[33,84,85],{},"Access is read-only and scoped to teams."," No key can create or change anything in Totem. A key reads only the teams of its integration, with administrator-level access to them, including channels and records marked Private, so give an integration only the teams it needs.",[30,88,89,92,93,97],{},[33,90,91],{},"A key can be bound to your servers."," An ",[16,94,96],{"href":95},"\u002Fdevelopers\u002Fauthentication#ip-allowlist","IP allowlist"," limits a key to the IPv4 addresses you list.",[30,99,100,103,104,108],{},[33,101,102],{},"Guessing is throttled."," Repeated failed attempts lock out the address or the key for a while, as ",[16,105,107],{"href":106},"\u002Fdevelopers\u002Fauthentication#failed-attempts","Failed attempts"," explains.",[22,110,112],{"id":111},"activity-records","Activity records",[12,114,115,116,118,119,122,123,126],{},"For each key, ",[33,117,67],{}," shows ",[33,120,121],{},"Last used",": when the key last made a request, and the ",[38,124,125],{},"\u002F24"," network the request came from. It can lag by up to 10 minutes.",[12,128,129,130,133,134,137],{},"Each integration's ",[33,131,132],{},"Recent activity"," shows its most recent changes, such as its creation, each key roll or revocation, and each change to its teams, its IP allowlist or its name, with the date and who made it. Totem keeps each of these entries for ",[76,135],{"name":136},"AUDIT_RETENTION_DAYS"," days.",[12,139,140,141,137],{},"Totem also counts each key's requests per day and endpoint, and keeps those counts for ",[76,142],{"name":143},"KEY_STATS_RETENTION_DAYS",[22,145,147],{"id":146},"where-the-api-runs","Where the API runs",[27,149,150,160,178,187,201],{},[30,151,152,155,156,159],{},[33,153,154],{},"Region."," The API runs on Google Cloud in the ",[38,157,158],{},"europe-west1"," region, in Belgium, and reads Totem's database, which is stored in the European Union.",[30,161,162,165,166,169,170,173,174,177],{},[33,163,164],{},"Encryption in transit."," ",[38,167,168],{},"api.totemstudio.ai"," answers only on port 443, over HTTPS with TLS 1.2 or later. There is no plain-HTTP listener, so a key never travels unencrypted, and the API's own responses carry ",[38,171,172],{},"Strict-Transport-Security"," and ",[38,175,176],{},"X-Content-Type-Options: nosniff",".",[30,179,180,183,184,177],{},[33,181,182],{},"No caching."," No CDN or shared cache sits in front of the API, and every response from the API itself carries ",[38,185,186],{},"Cache-Control: no-store",[30,188,189,192,193,196,197,200],{},[33,190,191],{},"No browsers."," The API sends no CORS headers and refuses a secret key sent with an ",[38,194,195],{},"Origin"," header (",[38,198,199],{},"400 secret_key_in_browser","), so a key pasted into a web page fails at once instead of working quietly.",[30,202,203,206,207,211],{},[33,204,205],{},"A firewall in front."," A firewall filters requests before they reach the API, as ",[16,208,210],{"href":209},"\u002Fdevelopers\u002Ferrors#error-format","Errors"," describes.",[22,213,215],{"id":214},"personal-data-you-receive","Personal data you receive",[12,217,218],{},"The API returns personal data: leads' names, email addresses, phone numbers, company and tags, where each lead came from (UTM values and ad, campaign and form names) and its activities, team members' display names, and the metadata of calls, conversations and bookings. It never returns message text, call transcripts or recordings. Once you copy this data, protecting it is up to you and the customer whose data it is. We recommend that you:",[27,220,221,227,233,249,259,265,275],{},[30,222,223,226],{},[33,224,225],{},"Use the data only for what the customer asked."," If you build the integration for a Totem customer, use what it returns only for that customer's purposes.",[30,228,229,232],{},[33,230,231],{},"Protect your copy."," Store it encrypted, limit who can read it, and keep records out of logs, error reports and support requests. Print ids, not whole records.",[30,234,235,238,239,243,244,248],{},[33,236,237],{},"Apply removals as soon as they arrive."," Delete the record from your copy and from every system you fed from it, and keep only its tombstone. ",[16,240,242],{"href":241},"\u002Fdevelopers\u002Fsync#removals","Removals"," explains, and ",[16,245,247],{"href":246},"\u002Fdevelopers\u002Ffaq#how-do-i-honor-erasure-requests","How do I honor erasure requests?"," covers erasures.",[30,250,251,254,255,108],{},[33,252,253],{},"Apply contact permissions first",", before any of your systems contacts a person, as ",[16,256,258],{"href":257},"\u002Fdevelopers\u002Fsync#contact-permissions-first","Contact permissions first",[30,260,261,264],{},[33,262,263],{},"Send opt-outs back."," The API is read-only, so Totem cannot learn about an opt-out recorded in your systems. When a person opts out there, make sure it is also recorded on the lead in Totem.",[30,266,267,270,271,108],{},[33,268,269],{},"Treat text fields as untrusted input",", as ",[16,272,274],{"href":273},"\u002Fdevelopers\u002Fconventions#text-fields-are-untrusted-input","Conventions",[30,276,277,280],{},[33,278,279],{},"Delete what you no longer receive."," When an integration is revoked or a team leaves it, Totem can no longer send you removals for that data: delete your copy, unless the customer asks you to keep it and has its own reason to. While access is refused, for example because the customer's plan no longer includes the API, no removals arrive either: if that lasts longer than your retention policy allows, delete your copy.",[22,282,284],{"id":283},"report-a-vulnerability","Report a vulnerability",[12,286,287,288,292,293,297,298,302],{},"If you find a security problem in the API, in these docs or in the ",[16,289,291],{"href":290},"\u002Fdevelopers\u002Ftypescript","TypeScript SDK",", write privately to ",[16,294,296],{"href":295},"mailto:hello@totemstudio.ai","hello@totemstudio.ai",". Say what you found and how to reproduce it with fictitious data, and include request ids when you have them. Never include a live key or real customer data, and do not report it in a public issue. If one of your keys leaked, follow ",[16,299,301],{"href":300},"\u002Fdevelopers\u002Fsupport#if-a-key-leaks","If a key leaks"," first.",{"title":304,"searchDepth":305,"depth":305,"links":306},"",3,[307,309,310,311,312],{"id":24,"depth":308,"text":25},2,{"id":111,"depth":308,"text":112},{"id":146,"depth":308,"text":147},{"id":214,"depth":308,"text":215},{"id":283,"depth":308,"text":284},"How Totem protects API keys and the data the API serves, what it records about each key, where the API runs, and what to do with the personal data you copy.",false,"md","en",{},true,"50","\u002Fdevelopers\u002Fsecurity","---\ntitle: Security and personal data\ndescription: How Totem protects API keys and the data the API serves, what it records about each key, where the API runs, and what to do with the personal data you copy.\nsection: guides\norder: 50\ntranslationKey: security\nlang: en\ndraft: false\n---\n\nThis page is for whoever reviews the security of an integration, on the customer's side or on yours. It sums up how Totem protects keys and data, and what to do with the personal data you copy. [Authentication](\u002Fdevelopers\u002Fauthentication) has the details of each control.\n\n## Keys\n\n- **A key is a long random secret.** It starts with `tsk_live_`, then a lookup id, then about 256 bits of random secret and a checksum. The checksum lets Totem refuse a mistyped key before looking it up.\n- **Totem never stores the key itself.** It keeps a one-way digest of the secret, computed with HMAC-SHA-256 and a separate secret that is kept outside Totem's database, so a copy of the database is not enough to use a key. Totem shows the key once, when it is created, and its logs name a key by its lookup id, never by its secret.\n- **Keys do not expire.** A key works until you roll it or revoke it. Roll keys on a schedule that suits you, for example every 90 days, and whenever someone who had access leaves; [Testing and go-live](\u002Fdevelopers\u002Ftesting#rehearse-a-key-rotation) shows how to rehearse it.\n- **Keys are managed only in Settings.** Creating, rolling and revoking keys happen in **Settings → Developers**, never through the API. Creating or rolling a key, adding a team and changing the IP allowlist ask the administrator to sign in again if they have not done so recently, and the administrators who manage the integration, and those of every team it covers, are told about every change, in Totem and by email.\n- **Revoking is fast.** A revoked key stops working within :api-const{name=\"KEY_CACHE_TTL_MS\" divide=\"1000\"} seconds, and revoking never needs a new sign-in, so it can be done at once during an incident.\n- **Access is read-only and scoped to teams.** No key can create or change anything in Totem. A key reads only the teams of its integration, with administrator-level access to them, including channels and records marked Private, so give an integration only the teams it needs.\n- **A key can be bound to your servers.** An [IP allowlist](\u002Fdevelopers\u002Fauthentication#ip-allowlist) limits a key to the IPv4 addresses you list.\n- **Guessing is throttled.** Repeated failed attempts lock out the address or the key for a while, as [Failed attempts](\u002Fdevelopers\u002Fauthentication#failed-attempts) explains.\n\n## Activity records\n\nFor each key, **Settings → Developers** shows **Last used**: when the key last made a request, and the `\u002F24` network the request came from. It can lag by up to 10 minutes.\n\nEach integration's **Recent activity** shows its most recent changes, such as its creation, each key roll or revocation, and each change to its teams, its IP allowlist or its name, with the date and who made it. Totem keeps each of these entries for :api-const{name=\"AUDIT_RETENTION_DAYS\"} days.\n\nTotem also counts each key's requests per day and endpoint, and keeps those counts for :api-const{name=\"KEY_STATS_RETENTION_DAYS\"} days.\n\n## Where the API runs\n\n- **Region.** The API runs on Google Cloud in the `europe-west1` region, in Belgium, and reads Totem's database, which is stored in the European Union.\n- **Encryption in transit.** `api.totemstudio.ai` answers only on port 443, over HTTPS with TLS 1.2 or later. There is no plain-HTTP listener, so a key never travels unencrypted, and the API's own responses carry `Strict-Transport-Security` and `X-Content-Type-Options: nosniff`.\n- **No caching.** No CDN or shared cache sits in front of the API, and every response from the API itself carries `Cache-Control: no-store`.\n- **No browsers.** The API sends no CORS headers and refuses a secret key sent with an `Origin` header (`400 secret_key_in_browser`), so a key pasted into a web page fails at once instead of working quietly.\n- **A firewall in front.** A firewall filters requests before they reach the API, as [Errors](\u002Fdevelopers\u002Ferrors#error-format) describes.\n\n## Personal data you receive\n\nThe API returns personal data: leads' names, email addresses, phone numbers, company and tags, where each lead came from (UTM values and ad, campaign and form names) and its activities, team members' display names, and the metadata of calls, conversations and bookings. It never returns message text, call transcripts or recordings. Once you copy this data, protecting it is up to you and the customer whose data it is. We recommend that you:\n\n- **Use the data only for what the customer asked.** If you build the integration for a Totem customer, use what it returns only for that customer's purposes.\n- **Protect your copy.** Store it encrypted, limit who can read it, and keep records out of logs, error reports and support requests. Print ids, not whole records.\n- **Apply removals as soon as they arrive.** Delete the record from your copy and from every system you fed from it, and keep only its tombstone. [Removals](\u002Fdevelopers\u002Fsync#removals) explains, and [How do I honor erasure requests?](\u002Fdevelopers\u002Ffaq#how-do-i-honor-erasure-requests) covers erasures.\n- **Apply contact permissions first**, before any of your systems contacts a person, as [Contact permissions first](\u002Fdevelopers\u002Fsync#contact-permissions-first) explains.\n- **Send opt-outs back.** The API is read-only, so Totem cannot learn about an opt-out recorded in your systems. When a person opts out there, make sure it is also recorded on the lead in Totem.\n- **Treat text fields as untrusted input**, as [Conventions](\u002Fdevelopers\u002Fconventions#text-fields-are-untrusted-input) explains.\n- **Delete what you no longer receive.** When an integration is revoked or a team leaves it, Totem can no longer send you removals for that data: delete your copy, unless the customer asks you to keep it and has its own reason to. While access is refused, for example because the customer's plan no longer includes the API, no removals arrive either: if that lasts longer than your retention policy allows, delete your copy.\n\n## Report a vulnerability\n\nIf you find a security problem in the API, in these docs or in the [TypeScript SDK](\u002Fdevelopers\u002Ftypescript), write privately to hello@totemstudio.ai. Say what you found and how to reproduce it with fictitious data, and include request ids when you have them. Never include a live key or real customer data, and do not report it in a public issue. If one of your keys leaked, follow [If a key leaks](\u002Fdevelopers\u002Fsupport#if-a-key-leaks) first.\n","guides",{"title":6,"description":313},"developers\u002Fsecurity","security","tyUSYL8SE_mTsAuUY7JnTnmtMJti0pSJ7ELvNHqQyg4",[328,341,361,408],{"section":329,"items":330},"start",[331,335,339],{"path":332,"title":333,"description":334},"\u002Fdevelopers","Overview","Read the leads, calls, conversations and bookings of your Totem teams from your own systems, and keep a copy in sync.",{"path":336,"title":337,"description":338},"\u002Fdevelopers\u002Fquickstart","Quickstart","Make your first requests to the Totem API in a few minutes. Check your key, find your team, list leads and start a sync.",{"path":18,"title":19,"description":340},"Secret keys and where they may live, the Totem-Team header for keys with several teams, IP allowlists, and how to roll or revoke a key.",{"section":322,"items":342},[343,345,349,353,357,360],{"path":290,"title":291,"description":344},"Use the official TypeScript client to read your Totem data, paginate lists and handle API errors from your server.",{"path":346,"title":347,"description":348},"\u002Fdevelopers\u002Fsync","Keep a copy in sync","Copy a team with the changes feed and keep the copy current. Apply pages safely, recover from errors, and start from a reference client that Totem tests.",{"path":350,"title":351,"description":352},"\u002Fdevelopers\u002Fpagination","Lists and pagination","How list endpoints return records a page at a time, how to read every page, and what order and filters a list supports.",{"path":354,"title":355,"description":356},"\u002Fdevelopers\u002Fcoverage","Data coverage","What Totem records, since when, and how to tell an empty value from a fact that was never recorded.",{"path":358,"title":57,"description":359},"\u002Fdevelopers\u002Ftesting","How to build and test an integration safely while the API has no test environment, and what to check before you go live.",{"path":320,"title":6,"description":313},{"section":362,"items":363},"reference",[364,367,370,374,379,384,389,394,399,404],{"path":365,"title":274,"description":366},"\u002Fdevelopers\u002Fconventions","The notation these docs use, and the rules every response follows: ids, timestamps, empty values, amounts, enums, versions, stability, errors and untrusted text.",{"path":368,"title":210,"description":369},"\u002Fdevelopers\u002Ferrors","The error format, how your integration should handle each kind of error, and every error code with when it happens and what to do.",{"path":371,"title":372,"description":373},"\u002Fdevelopers\u002Frate-limits","Rate limits","How much a key can read per minute and per day, what counts toward each limit, how to retry after a 429, and how long an initial sync takes.",{"path":375,"title":376,"description":377,"apiTag":378},"\u002Fdevelopers\u002Freference-account","Account, teams and members","Check that a key works and what it can read, list its teams with their time zones, list each team's members, and check that the API is up.","account",{"path":380,"title":381,"description":382,"apiTag":383},"\u002Fdevelopers\u002Freference-leads","Leads, boards and activities","List, fetch and search a team's leads and clients, read each lead's activities, and list the boards and stages they move through.","leads",{"path":385,"title":386,"description":387,"apiTag":388},"\u002Fdevelopers\u002Freference-calls","Calls","List and fetch a team's calls, with their direction, timing and a normalized outcome. No recordings or transcripts.","calls",{"path":390,"title":391,"description":392,"apiTag":393},"\u002Fdevelopers\u002Freference-conversations","Conversations","List and fetch a team's conversations on every channel, with their status, assignee, whether a person has taken them over and when the last messages came and went. Never message content.","conversations",{"path":395,"title":396,"description":397,"apiTag":398},"\u002Fdevelopers\u002Freference-bookings","Bookings and services","List and fetch a team's bookings, and list the services its customers can book.","bookings",{"path":400,"title":401,"description":402,"apiTag":403},"\u002Fdevelopers\u002Freference-sync","Changes feed","One endpoint that copies a team and then returns every change, page by page.","sync",{"path":405,"title":406,"description":407},"\u002Fdevelopers\u002Fobjects","All objects","Every object the API returns, field by field, with an example of each.",{"section":409,"items":410},"resources",[411,415,419,423],{"path":412,"title":413,"description":414},"\u002Fdevelopers\u002Fchangelog","Changelog","Changes to the Totem API, newest first, each under the date it shipped.",{"path":416,"title":417,"description":418},"\u002Fdevelopers\u002Fsupport","Support","How to get help with the Totem API, what to include in a request, and what to do if a key leaks.",{"path":420,"title":421,"description":422},"\u002Fdevelopers\u002Fglossary","Glossary","The terms these docs use, defined in one place, each with a link to the page that explains it.",{"path":424,"title":425,"description":426},"\u002Fdevelopers\u002Ffaq","FAQ","Short answers to the questions developers ask most about the Totem API, with links to the details.","\u002Fdevelopers\u002Fes\u002Fsecurity",{"ACTIVATION_MARGIN_MS":429,"ADMIN_ACCESS_DISCLOSURE_VERSION":430,"AUDIT_RETENTION_DAYS":431,"AUTH_FAILURE_LIMITS":432,"CAPTURE_LAG_FLOOR_MS":436,"CAPTURE_LAG_P99_MS":7,"CURSOR_MAX_AGE_DAYS":437,"DIRTY_TTL_DAYS":437,"FAMILY_TEAM_SCOPE":438,"FAMILY_TEAM_SCOPE_OPTIONS":439,"FEED_SETTLE_LAG_MS":441,"GATE_REFRESH_MS":429,"INTEGRATION_NAME_LENGTH":442,"INTEGRATION_PURPOSE_LENGTH":444,"KEY_CACHE_TTL_MS":447,"KEY_STATS_RETENTION_DAYS":448,"LEAD_RESPONSE_READ_BUDGET":449,"LIST_SCAN_MAX":452,"MAX_FAMILY_INTEGRATION_TEAMS":437,"MAX_INTEGRATIONS_PER_FAMILY":453,"MAX_IP_ALLOWLIST":434,"PAGE_LIMIT":454,"PLATFORM_CONFIG_TTL_MS":457,"REMOVAL_RETENTION_DAYS":458,"REQUEST_BODY_MAX_BYTES":459,"ROLL_GRACE_DEFAULT_HOURS":460,"ROLL_GRACE_HOURS":461,"SEARCH_MAX_RESULTS":453,"SENSITIVE_SECTOR_POLICY":464,"SENSITIVE_SECTOR_POLICY_OPTIONS":465,"SYNC_DAILY_BUDGET":467,"SYNC_VELOCITY":469,"TEAM_CACHE_TTL_MS":471},60000,"2026-10-17",365,{"perIpPerMinute":433,"perLookupIdPerMinute":434,"banMs":435},60,20,600000,300000,30,"explicit_list",[438,440],"all_family_teams",10000,{"min":308,"max":443},80,{"min":445,"max":446},1,280,5000,90,{"conversations":434,"messageReads":450,"calls":451},200,50,1000,10,{"default":455,"max":456},25,100,15000,45,16384,24,[462,445,460,463],0,168,"exclude",[464,466],"allow",{"floor":468,"recordsMultiplier":305},250000,{"perMinute":470,"burst":441},2000,30000,"1.0a","guide",[475,476,477,478,479],{"id":24,"text":25,"depth":308},{"id":111,"text":112,"depth":308},{"id":146,"text":147,"depth":308},{"id":214,"text":215,"depth":308},{"id":283,"text":284,"depth":308},{"path":358,"title":57,"section":322},{"path":365,"title":274,"section":362},[],1792502789912]