Authentication
API keys, OAuth 2.0 access tokens, scopes, organization binding, expiry, rotation and IP allow-lists.
SalesOS accepts three credentials:
| Credential | Header | Use it for |
|---|---|---|
| API key | X-API-Key: sk_... or Authorization: ApiKey sk_... | Integrations, scripts, back-office jobs |
| OAuth 2.0 access token | Authorization: Bearer ... | Apps that act on behalf of other people or organizations. See OAuth 2.0 |
| Session token (JWT) | Authorization: Bearer ... | The SalesOS web app. Not intended for integrations |
A request that presents an API key is authenticated by the key alone. A bad key is rejected and never falls back to a browser session.
What a key can do
A key acts as the user who created it, in the organization it was created in:
- It never has more access than that user: same role, same record visibility (own / team / all), same Profile and field-level security, same plan entitlements.
- If the owner is deactivated or leaves the organization, the key stops working (
401 API_KEY_OWNER_INACTIVE/API_KEY_OWNER_NOT_IN_ORG). - If its organization is suspended, the key stops working (
401 ORGANIZATION_SUSPENDED). - Keys can never call
/auth,/api-keys,/admin,/scim,/oauth,/oauth-apps,/connected-apps,/developer-orgs,/apps,/marketplace,/page-builderor/named-credentials(403 API_KEY_SCOPE_BLOCKED), so a leaked key can't mint sessions, more keys or OAuth clients, install apps, or repoint an outbound credential. Use a signed-in session for those.
Scopes
Scopes are granted per resource. READ_<RESOURCE> covers GET; WRITE_<RESOURCE> covers every method and implies read.
| Resource | Paths | Scopes |
|---|---|---|
| Leads | /v1/leads | READ_LEADS, WRITE_LEADS |
| Contacts | /v1/contacts | READ_CONTACTS, WRITE_CONTACTS |
| Accounts | /v1/accounts, /v1/companies | READ_ACCOUNTS, WRITE_ACCOUNTS |
| Opportunities | /v1/opportunities, /v1/deals | READ_OPPORTUNITIES, WRITE_OPPORTUNITIES |
| Products | /v1/products | READ_PRODUCTS, WRITE_PRODUCTS |
| Quotes | /v1/quotes | READ_QUOTES, WRITE_QUOTES |
| Orders | /v1/orders | READ_ORDERS, WRITE_ORDERS |
| Tasks | /v1/tasks | READ_TASKS, WRITE_TASKS |
| Activities | /v1/activities | READ_ACTIVITIES, WRITE_ACTIVITIES |
| Cases | /v1/cases | READ_CASES, WRITE_CASES |
| Custom objects | /v1/custom-objects | READ_CUSTOM_OBJECTS, WRITE_CUSTOM_OBJECTS |
| Custom fields | /v1/custom-fields | READ_CUSTOM_FIELDS, WRITE_CUSTOM_FIELDS |
| Webhooks | /v1/webhooks | READ_WEBHOOKS, WRITE_WEBHOOKS (or the older WEBHOOKS) |
| GraphQL | /v1/graphql | the READ_ / WRITE_ scope of each entity an operation touches |
| Bulk API | /v1/bulk | the scope of the job's object (WRITE_ to load, READ_ to export) |
| Running flows | /v1/flows, /v1/flows/interviews… | the scope of each record step |
| Anything else | * |
Action scopes open one capability each, rather than a resource:
| Scope | Opens |
|---|---|
INVOKE_FUNCTIONS | POST /v1/functions/{id}/invoke |
MANAGE_METADATA | every /v1/metadata route (retrieve, deploy, tests) |
PUBLISH_EVENTS | POST /v1/events/custom/{apiName}; implies READ_EVENTS |
READ_EVENTS | GET /v1/events/org-catalog and the GraphQL customEvents subscription |
Action scopes never raise the owner's role: the routes behind them still need the owner to be an org owner or admin where they say so.
Each operation in the reference lists the scope it needs
(x-api-key-scopes in the OpenAPI document). A missing scope is
403 API_KEY_SCOPE_INSUFFICIENT. OAuth access tokens use the same scopes and
rules (without * or the legacy scopes); a miss there is
403 OAUTH_SCOPE_INSUFFICIENT.
Keys created before per-resource scopes existed may carry the legacy read /
write scopes. These cover only the original resources (leads through
activities and webhooks). Orders, cases, custom objects and custom fields
always need an explicit scope.
Expiry
Keys expire on the date you set at creation (default 90 days; "never" is
allowed but discouraged). An expired key returns 401 API_KEY_EXPIRED. Settings →
API Keys flags keys expiring within 7 days.
Rotation
Rotate a key without downtime:
- In Settings → API Keys, choose Rotate on the key, or call
POST /v1/api-keys/{id}/rotatewith{"overlapHours": 24}. - SalesOS issues a new secret with the same settings (scopes, rate limit, IP allow-list, expiry). The response contains it once.
- The old secret keeps working for the overlap (0–168 hours, default 24), then expires on its own. During the overlap both secrets work.
- Deploy the new secret. To stop the old one early, revoke it.
After the overlap the old secret returns 401 API_KEY_EXPIRED, with a message
saying it was rotated. POST /v1/api-keys/{id}/regenerate (swap with no
overlap) is deprecated in favour of rotation.
Every create, rotate, revoke and delete is written to the audit log with the actor, IP address and user agent.
IP allow-list
Restrict a key to the addresses your integration runs from. Each entry is an IPv4 or IPv6 address or a CIDR block:
An empty list allows any address. A request from elsewhere gets 403 API_KEY_IP_NOT_ALLOWED. The address checked is the connecting client as seen by
SalesOS's edge, not a client-supplied X-Forwarded-For.
Usage
Settings → API Keys shows, per key, the request count, when it was last used and from which IP, its rate limit, and any calls to deprecated endpoints.