◆ Sapport for developers API reference Error catalog OpenAPI
Sections

#Authentication and Permissions

Status: Available — keys, scopes, permissions, the IP list. Planned — test keys and the sandbox.

Purpose: how integrations and keys work, what scopes mean, how the effective permissions of a request are computed, how to rotate and revoke keys, and what to do when a key leaks.

#The model: tenant → integration → keys

Tenant
 └── Integration "My CRM"  (a service account, no dashboard login)
      ├── integration role (a set of permissions)
      ├── Key A  (scopes, expiry, IP list, mode)
      └── Key B  (issued during rotation; Key A keeps working for up to 24 h)

#Key format

sap_<cell>_<mode>_<identifier>_<secret>
PartValuesMeaning
sapconstantPrefix. Secret scanners use it to find leaked keys.
cellru, en, idThe cell in which the key is valid.
modelive, testProduction or test (the sandbox is Planned).
identifier12 base32 charactersThe public key identifier (key_id). Visible in the dashboard and logs.
secretabout 43 base62 charactersThe secret part: 32 random bytes.

Example (deliberately fake):

sap_ru_test_EXAMPLE0KEYID_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEX

The platform stores only an irreversible hash of the secret with the cell's server-side pepper. A database leak without the pepper does not reveal the keys.

#How to send the key

Only in the Authorization header, and only over TLS:

GET /api/public/v1/me HTTP/1.1
Host: formula-cream.pro
Authorization: Bearer sap_ru_live_EXAMPLE0KEYID_…

#Key lifecycle

EventHow it works
IssuanceDashboard: Settings → API. The key is shown once; save it immediately. The event is written to the audit log and a notification goes to the tenant owner.
StorageThe customer's responsibility: a secret store or environment variables. Not in a repository, not in a frontend, not in logs.
Expiry365 days by default; you cannot set more than 365. The expiry is visible in GET /me (key.expires_at). The api_key.expiring webhook event arrives 30 and 7 days before expiry. A key that has expired gets the same response as a nonexistent one: 401 invalid_api_key.
RotationIssue a new key for the integration, switch your systems over, then revoke the old one. During rotation the old key keeps working for up to 24 hours, so the switch does not have to be instantaneous.
RevocationDashboard → the key → "Revoke". Takes effect within 60 seconds at most (the verification result is cached for up to a minute; the cache is cleared on revocation). Revocation is irreversible.
TrackingFor each key, the dashboard shows the time and (in hashed form) the address of the last use.

#Step-by-step rotation without downtime

  1. Issue a new key for the same integration with the same scopes.
  2. Deploy the new key to your secret store and restart the services.
  3. Use "API usage" in the dashboard to confirm that the old key is no longer called.
  4. Revoke the old key. If you forget, it expires on its own at the end of the overlap window.

#Scopes

A scope is a permission you give to a key. Grant the minimum necessary. The "Paid" column shows whether the tenant's wallet is charged. The "PII" column shows whether the responses contain personal data of the tenant's customers.

ScopeWhat it grantsPaidPII
conversations:readConversations and messagesnoyes
conversations:writeReceiving a customer message from your channel, an operator message, handoff, closing, taking over and releasing the AInoyes
ai_seller:invokeThe AI seller's reply to a customer message from your channelyesyes
leads:readLeads and contactsnoyes
leads:writeCreating and editing a lead, accepting into an external CRM, notes, tasks, confirming agent action requests, writing an external score (PUT /leads/{id}/score), the status map (PUT /crm/status-map)noyes
leads:scoreLead scoring by our modelyesyes
analytics:readTraffic aggregates, sources, funnel, visibilitynono
analytics:writeIngesting events and offline conversions; pseudonymous identifiers onlynono (see note 2)
dashboards:readReady-made "Overview" and "Pulse" viewsnono
articles:readArticles, revisions, publication statusesnono
articles:writeEditing, approval, publishingnono
articles:generateOrdering article generationyesno
strategist:readReports, the topic queue, chat actionsnono (note 1)
strategist:writeApproving and rejecting topics, confirming and canceling strategist actionsnono
strategist:chatA turn in the conversation with the strategistyesno (note 1)
webhooks:manageEvent subscriptions, the delivery lognono (note 6)

Notes.

  1. The strategist works with aggregates and, by design, does not see personal data; this is verified when the API is released.
  2. Event ingestion accepts only pseudonymous identifiers: anonymous_id and user_id are your own pseudonyms. Email, phone, and name in properties are rejected with 422 validation_failed (a JSON Pointer identifies the field), so this scope is not classified as PII.
  3. POST /leads/{id}/score is paid and requires leads:score. Writing the external CRM's own score (PUT /leads/{id}/score) is free and requires leads:write.
  4. Approving a strategist topic and confirming a strategist action additionally require the permission for what is being proposed and an available budget. Approving a topic does not start article generation.
  5. GET /me, GET /wallet, and GET /operations/{id} are available to any valid key and require no scope (GET /me and GET /wallet also work while exchange is suspended; see Wallet); operations are visible only to the integration that created them.
  6. A subscription with include_pii: true additionally requires the integration to have leads:read and/or conversations:read (Webhooks).
  7. The personal data disclosure log is visible to the tenant in the dashboard and through GET /disclosures (retained for 3 years). Which permission a key needs for this request is not fixed in v1 and will be specified in OpenAPI. For details, see Cells and data.

#Mapping scopes to endpoints

ScopeEndpoints
conversations:readGET /conversations, GET /conversations/{id}, GET /conversations/{id}/messages
conversations:writePOST /conversations, POST /conversations/{id}/messages (ingestion), POST /conversations/{id}/operator-messages, POST /conversations/{id}/takeover, /release, /close
ai_seller:invokethe AI reply in POST /conversations/{id}/messages (together with conversations:write)
leads:readGET /leads, GET /leads/{id}, GET /leads/by-external/{external_id}
leads:writePOST /leads, PATCH /leads/{id}, POST /leads/{id}/accept, POST /leads/{id}/notes, POST /leads/{id}/tasks, POST /leads/{id}/actions/{action_id}/ack, PUT /leads/{id}/score, PUT /crm/status-map
leads:scorePOST /leads/{id}/score
analytics:readGET /analytics/timeseries, /analytics/traffic-sources, /analytics/funnel, /analytics/visibility
analytics:writePOST /analytics/events, POST /analytics/conversions
dashboards:readGET /dashboards/overview, GET /dashboards/pulse
articles:readGET /articles, GET /articles/{id}, GET /articles/generation-jobs/{id}
articles:writePATCH /articles/{id}, POST /articles/{id}/approve, POST /articles/{id}/publish
articles:generatePOST /articles/generation-jobs (or generate: true when approving a strategist topic)
strategist:readGET /strategist/reports, GET /strategist/reports/{id}, GET /strategist/topics, GET /strategist/actions, GET /strategist/actions/{id}
strategist:writePOST /strategist/topics/{id}/approve, POST /strategist/topics/{id}/reject, POST /strategist/actions/{id}/confirm, POST /strategist/actions/{id}/cancel
strategist:chatPOST /strategist/chat/sessions, POST /strategist/chat/sessions/{id}/turns
webhooks:manageGET/POST/PATCH/DELETE /webhooks, POST /webhooks/{id}/test, POST /webhooks/{id}/rotate-secret, GET /webhooks/{id}/deliveries
no scopeGET /me, GET /wallet, GET /operations/{id}

#How the permissions of a request are computed

A request is allowed only if every condition is met. The effective permissions are an intersection:

request permissions = key scopes
                    ∩ integration role
                    ∩ CURRENT permissions of the owner employee
                    ∩ license (api_access) and plan
LayerWhat it means in practice
Key scopesSet at issuance. You cannot widen a scope; you issue a new key.
Integration roleThe maximum of what the integration can do in the tenant at all.
Owner's current permissionsChecked at request time, not at issuance. If the employee loses a permission, the integration loses it too. If the employee is deactivated, the keys of their integrations are suspended and the tenant owner receives an alert.
License and planThe api_access flag and plan restrictions. A refusal is 403 feature_not_in_plan.

In addition, the tenant check applies: the tenant must be active. The identifier of another tenant's resource yields 404 not_found.

The consequence for an integrator: a key that worked yesterday can get scope_missing today if the owner's permissions changed. This is normal behavior; handle 403.

#IP allowlist

#Test keys

Status: Planned.

Keys with the test mode (sap_<cell>_test_…) work in the cell's sandbox, a separate tenant with fictitious data.

PropertyTest key
Datafictitious; your production data is not affected
Paid operationsdo not charge money
AI sellerreplies with canned responses
Webhooksevents are delivered
Same codethe only difference is the key and the data

A production key does not work in the sandbox, and a test key does not work in the production tenant.

#What to do if a key leaks

  1. Revoke the key in the dashboard (Settings → API). Revocation takes effect within 60 seconds at most.
  2. Issue a new key and update the secrets in your systems.
  3. Check the log "API usage" (retained for 90 days; it holds the method, route, status, time, cost, and request_id, with no request bodies and no personal data) and the audit trail: what the key did after the suspected leak date.
  4. If the key had scopes with personal data, check the personal data disclosure log (dashboard or GET /disclosures, retained for 3 years) and assess which data may have been disclosed. The data controller's duties to notify regulators and data subjects are determined by the laws of your jurisdiction.
  5. Check whether any new webhooks, keys, or integrations were created, and revoke the extras; if in doubt, rotate the webhook secrets.
  6. Remove the key from the repository history and from logs; a single deletion commit is not enough.

Prevention: minimal scopes, an IP allowlist, a lifetime shorter than the maximum, separate keys for separate systems, storage in a secrets manager.

#Authentication errors

CodeHTTPWhen
invalid_api_key401The key is missing, wrong, revoked, or expired. The response is intentionally identical in all these cases
wrong_cell401A key from another cell; the response has a correct_base_url field with the right address
scope_missing403The key lacks the required scope, or it has been "cut down" by the role, the owner's permissions, or the plan
ip_not_allowed403The address is outside the allowlist
feature_not_in_plan403The plan or license does not include the API/feature
pii_transfer_not_allowed403RU cell: a scope with personal data or include_pii was requested for an integration whose hosting country is outside Russia

The response body is application/problem+json with a type of the form https://<cell domain>/developers/errors#<code> (Conventions). The full catalog is in Conventions.

#Notes