Skip to content

Authentication

Every request carries a bearer access token, obtained by exchanging a client credential at POST /oauth/token, or by an AI assistant signing you in with OAuth.

A credential is either a company credential or a user credential. Both use the same OAuth flow, the same scopes and the same endpoints; they differ in whose data the token reads.

Company credentialUser credential
Belongs toThe companyOne user of the company
ReadsEverything the company ownsWhat that user can see in Salesbud, nothing more
Created bySomeone who manages API credentialsAny active member who does not approve credentials, then approved by an administrator. An assistant signing you in with OAuth creates one too
ValidityChosen at creation, may never expire30, 90, 180 or 365 days, counted from the approval. A connection made with OAuth has no fixed validity
Typical useServer-to-server integrations, the shared MCP connectorA personal script, a local MCP client, Claude or ChatGPT signed in with OAuth

Neither kind is tied to a login session, a selected workspace or a parameter: what a token reads is decided on the server from the credential, and no input widens it.

GET /v1/context says which kind a token carries in client.kind, and for a user credential who owns it in client.owner. See Get the authenticated integration context.

  1. A member of a company with API access requests a credential on the API credentials screen, choosing a name, the scopes and the validity. Only scopes the member can use in Salesbud are offered; email and WhatsApp scopes need the matching access there.
  2. The client ID and secret are shown once, at that moment. The credential is inert until approved: POST /oauth/token answers invalid_client.
  3. An administrator approves or denies the request as it is. Scopes cannot be edited afterwards; a different set of scopes is a new request.
  4. Once approved, the same secret starts issuing tokens. Its validity of 30, 90, 180 or 365 days is counted from the approval, not from the request. A denied request is revoked.

Approvers do not get user credentials. When an administrator, or anyone allowed to manage API credentials, makes the same request, the credentials screen creates an active company credential instead, with no expiry unless they choose 30, 90, 180 or 365 days.

An AI assistant can also create a user credential for you by signing you in with OAuth instead of a copied secret. That connection follows the same approval and the same limits, except that an approver who connects gets an active user credential, not a company one. See Connect Claude or ChatGPT.

Salesbud‘s own apps, such as the AI collaborators, connect the same way with three differences: the company needs the AI collaborators feature instead of API access, and without it authorizing and renewing answer 403 AI_COLLABORATORS_DISABLED; the connection is active at once, with no approval; and it is left out of the per-user limits below, which it does not count toward either. It is listed on the API credentials screen like any other connection, and an administrator can disable or revoke it.

Each user can hold 3 active and 1 pending user credential in each company; a disabled credential counts as active. User credentials do not count toward the 20 active credentials of the company.

A user credential works only while its owner is an active member of the company. If the owner is deactivated or removed, or no longer has a Salesbud login, POST /oauth/token answers invalid_client with "error_description": "The credential has no active owner.", and a token already in hand stops working within 60 seconds: reads answer 401 INVALID_ACCESS_TOKEN. The credential stays listed until an administrator revokes it.

A connection made with OAuth has no secret to present, so it fails differently: renewing it with the refresh token answers invalid_grant, not invalid_client.

Terminal window
curl -X POST https://api.salesbud.com.br/oauth/token \
-d 'grant_type=client_credentials' \
-d "client_id=$SALESBUD_CLIENT_ID" \
-d "client_secret=$SALESBUD_CLIENT_SECRET"

The endpoint accepts only application/x-www-form-urlencoded. Credentials may also be sent with HTTP Basic instead of in the body.

200 OK
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "meetings.read calls.read transcriptions.read"
}

Tokens are signed RS256 and carry the company, the granted scopes and the credential version.

ScopeGrants
meetings.readList and retrieve meetings, their template answers and their overall evaluations.
calls.readList and retrieve calls, their template answers and their overall evaluations.
transcriptions.readRead transcripts, alongside the scope of the collection the transcript belongs to. Requested separately because a transcript is the most sensitive payload the API serves.

A transcript therefore needs two scopes: meetings.read and transcriptions.read for a meeting transcript, calls.read and transcriptions.read for a call transcript. transcriptions.read on its own reads nothing.

A token receives the scopes attached to its credential. Requesting a route outside them returns 403 INSUFFICIENT_SCOPE — that is the gate working, not a missing route.

A user credential’s token carries the requested scopes minus the ones its owner cannot use in Salesbud at issuance time. Asking for a scope the credential does not hold is still refused with 400 invalid_scope. If the owner loses email access, the next token comes without the emails.* scopes and the scope field of the token response says so. If the owner has lost the permissions for every scope of the credential, the token still comes back, with an empty scope, and every read answers 403 INSUFFICIENT_SCOPE until the permission returns. A connection made with OAuth is stricter: when none of its scopes is available, the token request is refused with 400 invalid_scope instead of returning an empty scope.

Call GET /v1/context to see the scopes a token actually carries.

At issuance, manual clients and third-party OAuth apps require API_ACCESS; first-party OAuth apps require AI_COLLABORATORS. For partner companies, the partner must also be active and the published offer must contain the feature required by that client. For these partner companies, the same feature is rechecked on every authenticated request: removing the required access rejects existing partner tokens with 403 API_ACCESS_DISABLED or 403 AI_COLLABORATORS_DISABLED, according to the client. Legacy token lifetimes are unchanged. A workspace header cannot switch the company bound to a credential.

Tokens expire after expires_in seconds (3600 by default).

Client credentials have no refresh token, by design. It is a machine-to-machine grant with no user to re-consent, so RFC 6749 §4.4.3 says not to issue one — a refresh token would be a second long-lived secret with the same power as the first. Renewal is calling /oauth/token again. Connections made by signing in with OAuth are different: they get a refresh token, valid once and for 90 days, because there is a person behind them and no secret to call with again.

Cache the token slightly under expires_in, and also re-issue when a request returns 401 INVALID_ACCESS_TOKEN. A credential can be revoked before the clock runs out; a timer alone will not notice.

A connection made by signing in ends with POST /oauth/revoke (RFC 7009): send its refresh token, or one of its access tokens, with the app’s client_id. The whole connection is revoked, as on the API credentials screen, and the answer is 200 with an empty body even for a token the server does not know. A connection an administrator disabled stays disabled when its owner or the app revokes it: its refresh tokens end, but only an administrator’s revocation moves it to revoked. See /oauth/revoke.

Rotating a company credential issues a new secret while the previous one keeps working for seven days, so you can deploy the new value without a window of failed requests.

Only the owner rotates a user credential. An administrator can disable or revoke it, but not rotate it. The seven days above apply to company credentials only: when the owner rotates a user credential, the previous secret stops issuing tokens at once, there is no overlap window, and the new secret gets a validity of 30, 90, 180 or 365 days counted from the rotation. Tokens already issued keep working until they expire.

A connection made with OAuth has no secret, so it cannot be rotated: asking for it is refused with 409 OAUTH_CLIENT_NOT_ROTATABLE. To cut it off, revoke it.

For a company credential, at most two secrets are valid at once. Revoke the expiring one before starting another rotation, otherwise the second rotation is refused with 409 ROTATION_IN_PROGRESS.

Revoking or disabling the whole client invalidates issued tokens immediately — that is the lever to pull on a suspected leak, not rotation.

A credential can carry an optional list of IPv4/IPv6 CIDRs. When set, it is enforced both when issuing a token and on every request that uses it, so a leaked token is useless from outside your network.