Quickstart
This walks from a fresh credential to a page of meetings. It takes two requests.
Before you start
Section titled “Before you start”You need a client ID and client secret, issued by Salesbud for your
company. Credentials are not self-service: ask your Salesbud contact, who will
also confirm that the API_ACCESS feature is enabled for your company.
The secret is shown once, at creation. Store it in a secret manager — it cannot be read back, only rotated.
-
Get an access token
Tokens are issued through OAuth 2.0 client credentials. Send the credential as form-encoded fields:
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"const response = await fetch("https://api.salesbud.com.br/oauth/token", {method: "POST",headers: { "Content-Type": "application/x-www-form-urlencoded" },body: new URLSearchParams({grant_type: "client_credentials",client_id: process.env.SALESBUD_CLIENT_ID,client_secret: process.env.SALESBUD_CLIENT_SECRET,}),});const { access_token, expires_in } = await response.json();import os, requestsresponse = requests.post("https://api.salesbud.com.br/oauth/token",data={"grant_type": "client_credentials","client_id": os.environ["SALESBUD_CLIENT_ID"],"client_secret": os.environ["SALESBUD_CLIENT_SECRET"],},)access_token = response.json()["access_token"]The endpoint requires
application/x-www-form-urlencoded. Sending JSON returns415 UNSUPPORTED_MEDIA_TYPE.200 OK {"access_token": "eyJhbGciOiJSUzI1NiIs...","token_type": "Bearer","expires_in": 3600,"scope": "meetings.read calls.read"} -
Confirm what the token can do
GET /v1/contextreports the company, the scopes and the effective rate limit behind the token. It is the fastest way to tell a configuration problem from an integration bug.Terminal window curl https://api.salesbud.com.br/v1/context \-H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" -
Read a page of meetings
Terminal window curl "https://api.salesbud.com.br/v1/meetings?meeting_after=2026-01-01T00:00:00Z&limit=50" \-H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN"Only meetings your company owns and that finished processing are returned. See Meetings and calls for what “finished” means and why VoIP records live at
/v1/callsinstead. -
Walk the rest of the history
Pass
next_cursorback unchanged, keeping every filter identical, and loop onhas_more— never on the size ofdata.let cursor = null;do {const url = new URL("https://api.salesbud.com.br/v1/meetings");url.searchParams.set("meeting_after", "2026-01-01T00:00:00Z");url.searchParams.set("limit", "50");if (cursor) url.searchParams.set("cursor", cursor);const page = await fetch(url, {headers: { Authorization: `Bearer ${accessToken}` },}).then((r) => r.json());for (const meeting of page.data) await handle(meeting);cursor = page.pagination.next_cursor;} while (cursor);A page can come back short — even empty — while
has_moreistrue. That is expected: the service caps how much it scans per request. Pagination explains the guarantees.
Renewing the token
Section titled “Renewing the token”There is no refresh token. Client credentials is a machine-to-machine grant with
no user to re-consent, so RFC 6749 §4.4.3
says not to issue one. When the token expires, call /oauth/token again with
the same credential.
In practice: cache the token for slightly less than expires_in, and re-issue on
401 INVALID_ACCESS_TOKEN rather than on a timer alone — a credential can be
revoked before the clock runs out.
If a model is the one reading
Section titled “If a model is the one reading”If the consumer is an agent rather than a service, you can skip the HTTP client entirely: the MCP server exposes these same operations as tools for Claude Desktop, Claude Code and Cursor, and takes care of token renewal and retries. You still need the credential above.
Common first-call failures
Section titled “Common first-call failures”| Response | What it means |
|---|---|
415 UNSUPPORTED_MEDIA_TYPE | The token request was sent as JSON. Use form encoding. |
401 INVALID_CLIENT | Wrong client ID or secret, or the credential was revoked. |
403 API_ACCESS_DISABLED | The company does not have the API_ACCESS feature. Retrying will not help. |
403 INSUFFICIENT_SCOPE | The token lacks a scope the route requires — calls.read for calls, and transcriptions.read on top of the collection scope for transcripts. |
404 RESOURCE_NOT_FOUND | The id does not exist, belongs to another company, or belongs to the other collection. |