# Salesbud API — full documentation Generated from https://docs.salesbud.com.br. Every page, in reading order. --- # Salesbud API Source: https://docs.salesbud.com.br/ A meeting recorded by the bot and a call captured from VoIP are different resources, so they live at different paths: `/v1/meetings` and `/v1/calls`. Same shape, own identifiers. A credential belongs to a company, never to a user. Every read is filtered by that company — there is no parameter that widens it. Resources are addressed by opaque, prefixed ids (`mtg_`, `call_`, `usr_`). Internal sequential ids are never exposed. Every page is available as plain Markdown, plus [`llms.txt`](/llms.txt) and [`llms-full.txt`](/llms-full.txt) for tools that read documentation directly. ## Your first request Exchange your credential for an access token, then read a page of meetings. ```bash title="First request" # 1. Get an access token (valid for 1 hour) 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" # 2. Read completed meetings from a period curl "https://api.salesbud.com.br/v1/meetings?meeting_after=2026-01-01T00:00:00Z&limit=50" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` The response is a page of meetings plus a cursor: ```json title="200 OK" { "data": [ { "id": "mtg_H9_chV2YG6UGE0n31AqvDQ", "object": "meeting", "title": "Discovery — Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "participants": [{ "email": "buyer@acme.com", "kind": "external" }], "owner": { "id": "usr_CKAyHd20jJb6GGJqhNF9vA", "email": "rep@yourcompany.com" }, "transcript": { "available": true, "status": "completed" } } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_..." } } ``` ## Where to go next | If you want to… | Read | | --- | --- | | Make your first call end to end | [Quickstart](/get-started/quickstart/) | | Understand credentials, tokens and scopes | [Authentication](/guides/authentication/) | | Know when a record is a `meeting` and when it is a `call` | [Meetings and calls](/guides/meetings-and-calls/) | | Walk a full history safely | [Pagination](/guides/pagination/) | | Handle failures correctly | [Errors](/guides/errors/) | | See every endpoint, field and status | [API reference](/reference/meetings/listmeetings/) | :::note[Access is granted per company] The API is gated by the `API_ACCESS` company feature. If your company does not have it enabled, token issuance fails with `403 API_ACCESS_DISABLED` — talk to your Salesbud contact rather than retrying. ::: --- # The answer object Source: https://docs.salesbud.com.br/objects/answer Returned by the template answer routes, one entry per question of the record's default template.
id string
Opaque answer identifier.
object meeting_answer
Resource type. Always meeting_answer, including for calls.
question object
The question this answers.
question.id string
Opaque question identifier, stable across records that share the template. It is the field to key on, not the text, which the company can reword.
question.text string
Question text as the template asks it, sanitized.
question.type text · option · multi_select · boolean · number
What the template expects as an answer. The answer itself always arrives as a string, so this is what says how to parse it.
question.order integer
Position of the question in the template, starting at zero.
answer string
Generated answer. A question that produced none is left out of the list rather than returned with a null answer.
updated_at string date-time
When the answer last changed.
:::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # The call object Source: https://docs.salesbud.com.br/objects/call Returned by every route under `/v1/calls`. A call answers the same five routes as a meeting and carries the same fields except `bot_history`; what else changes is which records the collection holds and how they usually look. Every field is documented on [the meeting object](/objects/meeting/). What follows is only what differs. ## What differs
object
**`call`, never `meeting`.** It is the field that says which resource you hold.
id
**Prefixed `call_`.** A `mtg_` id does not resolve under /v1/calls, and the reverse is also a 404.
participants
**Usually a single entry carrying `phone`.** A VoIP record identifies the other party by number, not by email.
type
**Usually `audio`, but not always.** Calls in video exist; `object` is what tells the resource apart, never `type`.
enablement.meeting_type
**Often `null`.** The classifier was built for sales meetings and does not label calls.
:::note[Fields a call does not have] `bot_history` is not on this object: a VoIP capture has no recording bot, so the field never applies and the response omits it rather than sending an empty array. A schema that validates a call rejects the field. `enablement.meeting_type` stays, and is often `null`. That null is legitimate — the sales-stage classifier does not label calls today, but it could. It is not the same case as the bot. ::: :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # The evaluation object Source: https://docs.salesbud.com.br/objects/evaluation Returned by the overall evaluation routes. Every field except `object` and `status` is null until the evaluation completes.
id string nullable
Opaque evaluation ID, null while no evaluation exists.
object overall_meeting_evaluation
Resource type. Always overall_meeting_evaluation, including for calls.
meeting_id string
The record this evaluation belongs to. Carries the prefix of its collection: mtg_ or call_.
status not_started · processing · completed · failed
Public processing state.
score integer nullable
Score from 0 to 10, or null until the evaluation completes.
justification string nullable
Why the score was given, or null until the evaluation completes.
created_at string date-time nullable
When the evaluation was created, or null when none exists yet.
updated_at string date-time nullable
When the evaluation last changed, or null when none exists yet.
:::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # The meeting object Source: https://docs.salesbud.com.br/objects/meeting Returned by every route under `/v1/meetings`. A call carries the same fields except `bot_history` — see [the call object](/objects/call/) for what differs.
id string
Always prefixed `mtg_`. A `call_` id does not address a meeting: the API refuses it rather than resolving it in the other collection.
object meeting
Resource type. A record captured from a VoIP integration is a `call`; everything else is a `meeting`. Independent of `type`, which describes the media: a call can be video and a meeting can be audio.
title string
Sanitized meeting title.
status completed
Only semantically completed meetings are public in V1.
type video · audio
Meeting media type.
audience internal · external
Meeting audience classification.
meeting_at string date-time
Meeting instant in UTC.
duration_seconds integer
Non-negative meeting duration.
no_show boolean
Whether the completed meeting had no attendee.
participants array of object
Normalized external participants.
owner object
The user the record belongs to. Company scoping follows this user's default company.
template object nullable
Public meeting template reference when available.
tags array of object
Public tag references.
customer_questions array of object
Customer questions detected in the call.
competitors array of object
Competitors mentioned in the call.
enablement object nullable
Enablement summary when available.
accounts array of object
Company-scoped accounts linked to the meeting.
overall_evaluation object
Overall evaluation readiness and score summary.
skill_scores array of object
Per-skill scores; empty when no skill evaluation exists.
transcript object
Transcript availability and its consistent processing state.
created_at string date-time
UTC instant when the meeting entered Salesbud.
updated_at string date-time
UTC instant of the last change to the meeting record itself. Informational only: it does not advance when a dependent resource such as a transcript, tag or answer changes without touching the meeting row, so it is not a reliable incremental-sync marker.
bot_history array of object
Recording bot events. Present only on a meeting: a VoIP capture has no bot, so a call does not carry this field at all.
## Nested objects ### Meeting participant
display_name string optional
Sanitized external participant name when available.
email string email optional
Normalized lowercase email when available.
phone string optional
Normalized phone number when available.
kind external
Participant classification in V1.
### Meeting owner
id string
Opaque owner identifier.
name string
Public owner display name.
email string email nullable
Normalized owner email, or null when legacy data is malformed.
team object nullable
Owner default team inside the authenticated company.
### Meeting owner team
id string
Opaque team identifier.
name string
Team display name.
role participant · leadership · observer
Role of the owner in the team. `participant` sees only their own meetings; `leadership` sees the team's and is visible to it; `observer` sees the team's without appearing to the other members.
is_default boolean
Whether this is the owner's default team in the company. False when the owner has no default team there and the API reported another one instead, which is why the field exists rather than being implied.
### Template reference
id string
Opaque public template identifier.
name string
Public display name.
answers array of object
Sanitized template questions and meeting answers.
### Meeting template answer
field string
Opaque public template question field identifier.
question string
Question text as it was asked, sanitized.
answer string
Answer generated for this question. A question that produced none is left out of the list rather than returned with a null answer.
### Tag reference
id string
Opaque public tag identifier.
name string
Public display name.
### Meeting customer question
question string
Question the customer asked during the conversation.
category string
Category the question was classified into.
### Meeting competitor
id string
Opaque competitor identifier.
name string
Competitor name as registered by the company, not as spoken.
### Meeting enablement
next_steps string nullable
Next steps agreed in the conversation, or null when none were identified.
meeting_type qualification · negotiation · proposal · closing · technical_meeting · questions_clarifications nullable
Sales stage the conversation was classified as, or null. Often null on calls: the classifier was built for sales meetings.
speaking_duration object
How long each side spoke.
speaking_duration.users array of object
One entry per company user who spoke. A user who did not speak is left out rather than reported with zero.
speaking_duration.users[].user object
A company user as the API exposes them anywhere other than `owner`: the same identifier, without the team.
speaking_duration.users[].duration_seconds integer
How long this user spoke, in seconds.
speaking_duration.others_seconds integer nullable
How long everyone outside the company spoke, in seconds, or null when the split was not measured. Not broken down per person: the other side is not identified by user.
### Public user reference A company user as the API exposes them anywhere other than `owner`: the same identifier, without the team.
id string
Opaque user identifier, the same one `owner.id` carries.
name string
Public user display name.
email string email nullable
Normalized lowercase user email, or null when legacy data is malformed.
### Account reference
id string
Opaque account identifier.
domain string
Account email domain, lowercased.
cnpj string nullable
Brazilian company registration number as stored, with or without punctuation, or null when unknown.
### Meeting skill score
user object
The user the score refers to.
skill object
The skill that was scored.
skill.id string
Opaque skill identifier.
skill.name string
Skill name as the company registered it.
score integer
Score from 0 to 10.
justification string nullable
Why the score was given, or null when the evaluation produced none.
### Meeting bot event
code string nullable
What happened to the recording bot, as a stable code.
subcode string nullable
Further detail on the event, or null when the code carries none.
occurred_at string date-time
When the event happened.
:::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # The pagination object Source: https://docs.salesbud.com.br/objects/pagination Accompanies every list response. Drive the loop by `has_more`, never by the size of `data` — see [Pagination](/guides/pagination/).
limit integer
Number of resources requested.
has_more boolean
Whether another page is available. A page can be short or empty while this is true, so always continue while it is true instead of relying on the size of data.
next_cursor string nullable
Signed opaque cursor for the next page.
:::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # The transcript object Source: https://docs.salesbud.com.br/objects/transcript Returned by the transcript routes. An unavailable transcript is a successful response with `available: false`, not an error.
object transcript
Resource type. Always `transcript`, including for a call.
meeting_id string
The record this transcript belongs to. Carries the prefix of its collection: `mtg_` or `call_`.
available true · false
Whether there is a transcript to read. `false` is a successful response carrying the reason in `status`, not an error.
status completed · not_started · processing · failed
Processing state. Always `completed` while `available` is true; otherwise why the transcript is not there yet.
variant enhanced · original nullable
Which transcript was returned: `enhanced` when the improved version exists, `original` otherwise. Null while none is available.
utterances array of object
The transcript itself, in chronological order. Always empty while the transcript is unavailable.
created_at string date-time nullable
When the transcript was first stored, or null when none exists yet.
updated_at string date-time nullable
When the transcript last changed, or null when none exists yet.
## Nested objects ### Meeting transcript utterance
speaker string nullable
Sanitized speaker label when available.
text string
Sanitized utterance text.
start_ms integer
Utterance start offset in milliseconds.
end_ms integer
Utterance end offset in milliseconds.
:::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Quickstart Source: https://docs.salesbud.com.br/get-started/quickstart This walks from a fresh credential to a page of meetings. It takes two requests. ## 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. 1. **Get an access token** Tokens are issued through OAuth 2.0 client credentials. Send the credential as form-encoded fields: ```bash 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" ``` ```js 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(); ``` ```python import os, requests response = 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 returns `415 UNSUPPORTED_MEDIA_TYPE`. ```json title="200 OK" { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "scope": "meetings.read calls.read" } ``` 2. **Confirm what the token can do** `GET /v1/context` reports 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. ```bash curl https://api.salesbud.com.br/v1/context \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` 3. **Read a page of meetings** ```bash 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](/guides/meetings-and-calls/) for what "finished" means and why VoIP records live at `/v1/calls` instead. 4. **Walk the rest of the history** Pass `next_cursor` back unchanged, keeping every filter identical, and loop on `has_more` — never on the size of `data`. ```js 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_more` is `true`. That is expected: the service caps how much it scans per request. [Pagination](/guides/pagination/) explains the guarantees. ## 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](https://www.rfc-editor.org/rfc/rfc6749#section-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 If the consumer is an agent rather than a service, you can skip the HTTP client entirely: the [MCP server](/guides/mcp/) 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 | 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. | --- # Authentication Source: https://docs.salesbud.com.br/guides/authentication Every request carries a bearer access token, obtained by exchanging a client credential at `POST /oauth/token`. ## A credential is a company, not a user The credential identifies **your company**. It is not tied to a person, a login, a team or a selected workspace, and it does not inherit anybody's permissions. Everything the token can read is scoped to the company it was issued for, and no parameter widens that. This is why there is no user login flow: there is no user. ## Getting a token ```bash 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. ```json title="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. ## Scopes | Scope | Grants | | --- | --- | | `meetings.read` | List and retrieve meetings, their template answers and their overall evaluations. | | `calls.read` | List and retrieve calls, their template answers and their overall evaluations. | | `transcriptions.read` | Read 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. Call `GET /v1/context` to see the scopes a token actually carries. ## Lifetime and renewal Tokens expire after `expires_in` seconds (3600 by default). **There is no refresh token, by design.** Client credentials is a machine-to-machine grant with no user to re-consent, so [RFC 6749 §4.4.3](https://www.rfc-editor.org/rfc/rfc6749#section-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. 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. ## Rotating a secret Rotation 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. 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. ## IP allowlists 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. :::caution[The secret is shown once] It is stored only as an HMAC-SHA256 hash with a service-side pepper and cannot be read back. If it is lost, rotate — there is no recovery path. ::: --- # Errors Source: https://docs.salesbud.com.br/guides/errors Errors use one envelope across the API: ```json { "error": { "type": "ForbiddenError", "title": "Forbidden", "detail": "The transcriptions.read scope is required.", "code": "INSUFFICIENT_SCOPE", "request_id": "req_9bfbe80ac1f24e0a9d5b1e5e7a0f2c11" } } ``` ## Branch on `code` `code` is the stable, machine-readable field. It does not change with wording, translation or refactoring. `title` and `detail` are human text and **may be reworded** between releases. `type` is the error class and is stable, but coarser than `code` — several codes share one class. Never parse `detail`. Quote `request_id` when contacting support: it identifies the exact request in our logs. ## Codes | HTTP | `code` | Meaning | | --- | --- | --- | | 400 | `INVALID_LIMIT` | Page size outside 1–100. | | 400 | `INVALID_DATETIME` | A timestamp is not RFC 3339 with an explicit offset. | | 400 | `INVALID_FILTER` | A filter value or combination is unsupported. | | 400 | `INVALID_CURSOR` | The cursor was edited, or the filters changed mid-traversal. | | 400 | `INVALID_REQUEST` | Request validation failed, including unknown query parameters. | | 401 | `MISSING_ACCESS_TOKEN` | No bearer token was sent. | | 401 | `INVALID_ACCESS_TOKEN` | The token is invalid, expired, or its credential was revoked. | | 403 | `INSUFFICIENT_SCOPE` | The token does not carry the scope the route requires. | | 403 | `API_ACCESS_DISABLED` | The company does not have API access enabled. | | 404 | `RESOURCE_NOT_FOUND` | Absent, owned by another company, or addressed in the wrong collection. | | 413 | `PAYLOAD_TOO_LARGE` | The request body exceeds the accepted size. | | 415 | `UNSUPPORTED_MEDIA_TYPE` | Wrong content type — most often JSON sent to `/oauth/token`. | | 429 | `RATE_LIMIT_EXCEEDED` | A rate-limit policy rejected the request. | | 503 | `SERVICE_UNAVAILABLE` | A fail-closed dependency is unavailable. Retry with backoff. | | 503 | `AUDIT_UNAVAILABLE` | A successful read could not be recorded in the audit trail, so it was not returned. | ## Unknown identifiers are always 404 An id belonging to another company returns `404 RESOURCE_NOT_FOUND`, not `403`. The API does not confirm that a resource exists somewhere else — a `403` would leak that fact to anyone probing ids. ## The token endpoint is different `POST /oauth/token` returns the OAuth 2.0 error shape that [RFC 6749 §5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2) mandates, so standard OAuth clients keep working: ```json { "error": "invalid_client", "error_description": "Client authentication failed." } ``` ## What is never in an error Stack traces, source paths, tokens, secrets, request bodies and domain payloads are never returned. An unexpected failure returns a fixed generic message with a `request_id`; the detail stays in our logs. ## Retrying | Status | Retry? | | --- | --- | | `400`, `403`, `404`, `413`, `415` | No. The request will fail identically. | | `401` | Once, after re-issuing the token. | | `429` | Yes, after `Retry-After`. | | `503` | Yes, with exponential backoff and jitter. | --- # MCP server Source: https://docs.salesbud.com.br/guides/mcp This API serves a [Model Context Protocol](https://modelcontextprotocol.io) server over HTTP at `https://api.salesbud.com.br/mcp`. Registered once as a custom connector in claude.ai, it lets the model read meetings, calls, transcripts, template answers, evaluations, email and WhatsApp conversations with your credential — nothing to install. It is a client of the public API, not a new surface. Same OAuth credential, same scopes, same rate limits, **read operations only** — nothing it can reach is anything the HTTP API would not return to the same credential. ## Before you start You need an API credential with the scopes you intend to use, the `API_ACCESS` feature enabled for your company, and owner access to the Claude organization that will hold the connector. Credentials are not self-service; see the [Quickstart](/get-started/quickstart/). ## Add the connector An owner of the Claude organization adds a custom connector pointing at `https://api.salesbud.com.br/mcp`. Authentication is **No sign-in** — this server has no OAuth flow — and the credential of the company whose data the connector should read travels in two request headers: | Header | Value | | ----------------- | --------------- | | `X-Client-Id` | `sb_client_...` | | `X-Client-Secret` | `sb_secret_...` | Any other HTTP client may send the same credential as RFC 7617 Basic instead: ``` Authorization: Basic ``` Two consequences of a credential entered once by an administrator: - **It is stored on Anthropic's side and shared by the whole Claude organization.** Everyone in it reads whatever the scopes allow, and the audit trail records the credential, not the person who asked. - **Revoking means rotating.** Rotating the secret breaks every other integration using the same credential, so give the connector one of its own. If the credential restricts access by IP, the allowlist has to include Anthropic's egress range, `160.79.104.0/21`: the request reaches this API from there, not from the user's browser. ## Tools One tool per public read operation. The scope column is what the credential has to carry — a missing scope comes back as a readable `INSUFFICIENT_SCOPE` message the model can report, not an exception. | Tool | Scope | | -------------------------------- | ----------------------------------------- | | `get_api_context` | none beyond a valid credential | | `list_meetings` | `meetings.read` | | `get_meeting` | `meetings.read` | | `get_meeting_overall_evaluation` | `meetings.read` | | `get_meeting_answers` | `meetings.read` | | `get_meeting_transcript` | `meetings.read` + `transcriptions.read` | | `list_calls` | `calls.read` | | `get_call` | `calls.read` | | `get_call_overall_evaluation` | `calls.read` | | `get_call_answers` | `calls.read` | | `get_call_transcript` | `calls.read` + `transcriptions.read` | | `list_emails` | `emails.read` | | `get_email` | `emails.read` | | `list_email_messages` | `emails.read` + `emails.content.read` | | `list_whatsapp_conversations` | `whatsapp.read` | | `get_whatsapp_conversation` | `whatsapp.read` | | `list_whatsapp_messages` | `whatsapp.read` + `whatsapp.content.read` | Ask `get_api_context` first when you are unsure what a credential reaches: it answers with the client, the company, the scopes and the per-minute limit. `/oauth/token` is deliberately not a tool — the server issues and renews the token itself, and exposing it would burn the OAuth bucket and put the token in the conversation. The liveness and readiness probes are not tools either. ## Transcripts arrive in windows A one-hour transcript is thousands of utterances and does not belong in a model's context in one piece. The transcript tools return 200 utterances per call and say how many are left; the model passes `offset` to continue. The API returns the transcript whole — this windowing lives in the MCP server. A record with no transcript is not an error: the tool reports `transcript unavailable (status: not_started)`, which is the contract described in [Meetings and calls](/guides/meetings-and-calls/). ## Email bodies cost more than email metadata `list_emails` and `get_email` return conversation metadata — participants, connected mailboxes, linked accounts, counts — and never a body. Reading the messages is a separate tool, `list_email_messages`, and it asks for more: `emails.content.read` on top of `emails.read`, plus its own rate-limit policy, tighter than the rest of the API. Ask for a page at a time instead of walking a whole mailbox. A body longer than 2,000 characters is trimmed in the text the model reads and kept whole in `structuredContent`. Attachment content, HTML bodies and `bcc` are never served by the API, so no tool reaches them. ## WhatsApp text costs more than WhatsApp metadata `list_whatsapp_conversations` and `get_whatsapp_conversation` return conversation metadata — contact, seller, linked accounts, activity instants — and never a message. Reading the messages is a separate tool, `list_whatsapp_messages`, and it asks for more: `whatsapp.content.read` on top of `whatsapp.read`, plus its own rate-limit policy. Ask for a page at a time instead of walking a whole history. A text or an audio transcription longer than 2,000 characters is trimmed in the text the model reads and kept whole in `structuredContent`. A message deleted for everyone stays as a tombstone with no content; an edited message shows its current text. Media content, provider identifiers, reactions and read state never reach the model — the API does not serve them. See the [WhatsApp reference](/reference/whatsapp/listwhatsappconversations/). ## What the server handles, and what it does not It handles token renewal with a safety margin, one retry on `401` with a fresh token, `Retry-After` on `429`, backoff on `503`, and immediate failure on the other `4xx`. Errors reach the model carrying `code` and `request_id`, so a bad date filter comes back as `INVALID_DATETIME` and the model corrects the format itself. It does **not** paginate for you. The model follows `has_more` and `next_cursor` like any other client, and the tool description says so, because [a short page is not the last page](/guides/pagination/). It also does not give you incremental sync, because v1 has none: window by `meeting_at` or `created_at` and reprocess idempotently by `id`. ## Shared quota Every credential of a company draws from the same 600 requests/minute company bucket. An agent walking a long history consumes quota your other integrations are using; see [Rate limits](/guides/rate-limits/). --- # Meetings and calls Source: https://docs.salesbud.com.br/guides/meetings-and-calls A recording captured by the meeting bot and a recording captured from a VoIP integration are different things in the product, so they are different resources in the API. | | Meetings | Calls | | --- | --- | --- | | Collection | `/v1/meetings` | `/v1/calls` | | `object` | `meeting` | `call` | | Identifier prefix | `mtg_` | `call_` | | Where it comes from | Meeting bot, platform upload, mobile app | VoIP integration | Both support the same five routes — list, retrieve, transcript, template answers and overall evaluation — and carry the same fields except `bot_history`, which only a meeting has. What else changes is the collection and the identifier prefix. ## Do not infer the kind from the media type `object` and `type` are independent axes: - `object` says **what the record is** — `meeting` or `call`. - `type` says **how it was captured** — `video` or `audio`. A call can be video, and a meeting can be audio. In one production company, 3 of 49 calls are video and 6 of 863 meetings are audio. Branching on `type` to guess the kind gets those wrong. ```json { "id": "call_IGmhT13We_ltOPvjNSCHKw", "object": "call", "type": "audio" } { "id": "call_J781tlst89klU_mWPMXgDA", "object": "call", "type": "video" } ``` ## Identifiers do not cross collections An id addresses a resource in exactly one collection. Asking the wrong one is a `404`, not a redirect: ```bash curl ".../v1/calls/mtg_H9_chV2YG6UGE0n31AqvDQ" -H "Authorization: Bearer $TOKEN" # 404 RESOURCE_NOT_FOUND ``` This is deliberate: it keeps an id from silently resolving to a resource of a different kind, and it makes a mis-routed request obvious in your logs instead of returning something plausible. ## What is visible A record appears in the API only when it is **completed** — the processing that produces transcripts, answers and evaluations has finished. Records still recording, processing or failed are not returned at all, rather than exposed with a partial `status`. `status` is therefore always `completed` in v1. Ownership follows the meeting owner's **default company**. If a user's default company changes, their historical records move with them. This is a known trade-off of resolving ownership at read time; see [Security](/guides/security/). ## Choosing a collection Read `/v1/meetings` for sales conversations captured by the bot or uploaded, and `/v1/calls` for phone conversations captured through VoIP. If you want both, page each collection independently — there is no combined endpoint, because the two have different volumes and different retention in practice. :::tip[Fields that behave differently on calls] A call does not carry `bot_history`: there is no recording bot in a VoIP capture, so the field is omitted rather than empty — see [the call object](/objects/call/). `participants` on a call is usually a single entry carrying `phone` rather than `email`, because a VoIP record identifies the other party by number. `enablement.meeting_type` is often `null` on calls: the classifier was trained on sales meetings and does not label calls. ::: --- # Pagination Source: https://docs.salesbud.com.br/guides/pagination Lists are paginated with signed keyset cursors ordered by ascending id. The default page size is 50 and the maximum is 100. ```json { "data": [ /* … */ ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_..." } } ``` ## Loop on `has_more`, never on `data.length` This is the single most common integration bug against this API. A page can come back **short, or even empty, while `has_more` is `true`**. The service caps how much it scans per request, so when a window is sparse the remaining rows are served on the next call rather than in a slower single response. ```js // Correct let cursor = null; do { const page = await fetchPage(cursor); await handle(page.data); cursor = page.pagination.next_cursor; } while (cursor); ``` ```js // Wrong — stops early on a sparse window let page = await fetchPage(); while (page.data.length > 0) { /* … */ } ``` ## The cursor binds your filters `next_cursor` is signed and covers every filter of the request that produced it. Send it back **unchanged**, with **identical filters**. Changing a filter mid-walk, or editing the cursor, returns `400 INVALID_CURSOR` instead of silently returning a different slice. To change filters, start a new traversal from no cursor. ## What ordering by id guarantees Ordering by ascending id makes a traversal stable without a snapshot: records created while you paginate get higher ids, so they land after your current position and never shift a page you already read. The trade-off is the mirror image: a record that becomes **completed** after you have already passed its id is not picked up by the traversal in progress. It appears on your next pass over that window. This is why v1 has no incremental sync — there is no dedicated "became visible" timestamp to make a delta honest. `updated_at` is informative only; do not use it as a sync watermark. Re-query by period instead: ```bash curl ".../v1/meetings?meeting_after=2026-01-01T00:00:00Z&meeting_before=2026-02-01T00:00:00Z" ``` ## Filters | Parameter | Filters on | | --- | --- | | `meeting_after` / `meeting_before` | When the meeting or call happened. The filter most integrations want. | | `created_after` / `created_before` | When the record was created in Salesbud. | | `owner_email` | Exact, case-insensitive owner match. | | `type` | `video` or `audio` — the media, not the resource kind. | | `audience` | `internal` or `external`. | | `has_transcript` | Whether a transcript resource exists. | All timestamps are RFC 3339 with an explicit offset. `2026-01-01T00:00:00Z` is valid; `2026-01-01` is not, and returns `400 INVALID_DATETIME`. --- # Rate limits Source: https://docs.salesbud.com.br/guides/rate-limits Requests are metered with distributed token buckets. Several apply at once, and the strictest one wins. | Bucket | Applies to | Quota | | --- | --- | --- | | `client` | Every data-plane request, per credential | 120/min, adjustable per credential | | `company` | Every data-plane request, across all credentials of the company | 600/min | | `transcript` | Transcript reads only, on top of the two above | 60/min | | `oauth-client` | `POST /oauth/token`, per client id | 10/min | | `oauth-ip` | `POST /oauth/token`, per source IP | 30/min | A company may hold up to 20 active credentials, and together they never exceed the company quota. Call `GET /v1/context` to read the credential quota actually in effect for yours. ## Headers Every response carries the current state. `RateLimit-Policy` lists every bucket the request consumed; `RateLimit` reports the single one closest to exhaustion: ```http RateLimit-Policy: "client";q=120;w=60, "company";q=600;w=60 RateLimit: "client";r=118;t=1 ``` `q` is the quota, `w` the window in seconds, `r` what is left and `t` the seconds until another token. `RateLimit` names whichever bucket is holding the fewest tokens: normally your credential's, the transcript one on transcript reads, and the company one once sibling credentials are spending it. Pace against the ceilings in `RateLimit-Policy` — the bucket named in one reading is not the only one binding you. `r` counts tokens in the bucket, whose capacity is the burst rather than the quota, so it does not start at `q` and count down. Treat it as room available right now. A rejected request returns `429 RATE_LIMIT_EXCEEDED` with `Retry-After` in seconds. Wait that long — do not retry immediately, and add jitter so a fleet of workers does not resynchronise into the next window. ## Behaviour when the limiter is degraded Read paths **fail open**: if the limiter is briefly unreachable, business reads keep serving rather than going dark. Token issuance, transcript reads and internal control-plane routes **fail closed** with `503 SERVICE_UNAVAILABLE`. These are the paths where serving unmetered traffic is worse than serving none. ## The WAF limit is separate A coarse per-IP limit runs at the WAF, before traffic reaches the service, and it is not part of the quotas above: roughly 2000 requests per five minutes per source IP, and 100 per five minutes on `/oauth/token`. It fails differently, which matters when you meet it: - it blocks with `403`, not `429`, so it reads as an authorization failure; - there is no error code, no `Retry-After` and no rate limit headers, because the request never reaches the service; - it counts by source IP, so every integration behind the same egress address shares it, whatever credentials they use. A `403` with none of our error body, especially on `/oauth/token`, is this limit rather than a missing scope. ## Staying under the limit - Page with `limit=100` instead of many small pages — one request returns up to 100 records at the same cost as one returning 10. - Filter by period rather than walking the whole history repeatedly. - Cache the access token; every `/oauth/token` call consumes its own bucket. - Fetch transcripts only for records you will actually process. `has_transcript` lets you filter the list first. --- # Security Source: https://docs.salesbud.com.br/guides/security ## Company isolation Every read is filtered by the company on the access token. The company comes from the token claim, never from a request parameter, so there is no input that widens the scope of a credential. Resources are addressed by opaque, prefixed identifiers (`mtg_`, `call_`, `usr_`, `tpl_`) that are allocated per company. Internal sequential ids are never exposed, so record volume cannot be inferred and ids cannot be enumerated. An identifier your company does not own returns `404`, never `403` — the API does not confirm that a resource exists elsewhere. ## Ownership is resolved when you read A record belongs to the **default company of the user who owns it**, evaluated at query time rather than frozen when the record was created. The consequence is worth stating plainly: **if a user's default company changes, their historical records move with them.** A rep who moves between companies in the same group takes their meeting history to the new company's API. This is a deliberate trade-off of not denormalising ownership onto the record. If that behaviour does not fit your data agreement, raise it with your Salesbud contact before building on it. ## Transcripts are gated separately `transcriptions.read` is a distinct scope from `meetings.read` and `calls.read`, and a credential can hold the collection scopes without it. A transcript is the most sensitive payload the API serves — verbatim customer speech — so access to the metadata of a meeting does not imply access to what was said in it. The gate composes rather than replacing the collection scope: a transcript request is checked against both, so a credential limited to calls cannot reach a meeting transcript by holding `transcriptions.read`. Transcript text is sanitised before it leaves the service. ## Everything is audited Every business read is recorded: which credential, which company, which route, which resource ids, and the calling identity. The audit write is **fail closed**. If the trail cannot be written, the read is not returned — you get `503 AUDIT_UNAVAILABLE` instead. A successful response therefore means the access was recorded. ## Credential hygiene - Secrets are stored only as HMAC-SHA256 hashes with a service-side pepper. They cannot be read back, only rotated. - Rotation keeps the previous secret valid for seven days so you can deploy without downtime; revoke it once the new one is live. - On a suspected leak, **revoke the client** — that invalidates issued tokens immediately. Rotation does not, because the old secret stays valid. - Set an IP allowlist when your integration runs from fixed addresses. It is enforced at token issuance and on every request, so a leaked token is useless from elsewhere. ## What never leaves the service Stack traces, source paths, internal ids, tokens, secrets and request bodies are never returned in a response, and never written to logs. --- # Issue an access token Source: https://docs.salesbud.com.br/reference/authentication/issueaccesstoken post {"/oauth/token"} ### Authorization No authentication. ### Example request ```bash curl -X POST https://api.salesbud.com.br/oauth/token \ -d 'grant_type=client_credentials' \ -d 'client_id=sb_client_...' \ -d 'client_secret=sb_secret_...' ``` ### Example response ```json title="200 OK" { "access_token": "eyJhbGciOiJSUzI1NiJ9.EXAMPLE_PAYLOAD.EXAMPLE_SIGNATURE", "token_type": "Bearer", "expires_in": 3600, "scope": "meetings.read calls.read transcriptions.read" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Access token issued. | | `400` | OAuth client-credentials request rejected. | | `401` | OAuth client authentication failed. | | `413` | The request body is larger than the accepted limit. | | `415` | The request media type is not supported by this endpoint. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get one completed call Source: https://docs.salesbud.com.br/reference/calls/getcall get {"/v1/calls/{call_id}"} Returns a single [call object](/objects/call/). ### Authorization Requires the `calls.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `call_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": "call_ExampleCall01", "object": "call", "title": "Call to +55 11 99999-0000", "status": "completed", "type": "audio", "audience": "external", "meeting_at": "2026-01-15T18:28:01.000Z", "duration_seconds": 714, "no_show": false, "participants": [ { "phone": "+5511999990000", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Wants a demo before renewing the current contract." } ] }, "tags": [], "customer_questions": [], "competitors": [], "enablement": { "next_steps": "Online meeting booked for Thursday at 13:30; send the invite today.", "meeting_type": null, "speaking_duration": { "users": [], "others_seconds": 677 } }, "accounts": [], "overall_evaluation": { "status": "not_started", "justification": null }, "skill_scores": [], "transcript": { "available": false, "status": "not_started" }, "created_at": "2026-01-15T19:22:08.000Z", "updated_at": "2026-01-15T19:22:53.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped call. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List the default template answers for a call Source: https://docs.salesbud.com.br/reference/calls/getcallanswers get {"/v1/calls/{call_id}/answers"} Returns a list of [answer objects](/objects/answer/). ### Authorization Requires the `calls.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `call_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/answers" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "ans_ExampleAnswer01", "object": "meeting_answer", "question": { "id": "qst_ExampleQuestion01", "text": "What pains did the customer describe?", "type": "text", "order": 1 }, "answer": "Manual CRM entry after every call, about two hours a day.", "updated_at": "2026-01-14T14:10:43.000Z" } ], "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Answers generated for the call owner context. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get a call transcript Source: https://docs.salesbud.com.br/reference/calls/getcalltranscript get {"/v1/calls/{call_id}/transcript"} Returns the enhanced transcript when available, otherwise the original transcript. An unavailable transcript is represented by a successful response with available set to false and its current processing status. Returns a single [transcript object](/objects/transcript/). ### Authorization Requires the `calls.read` and `transcriptions.read` scopes together. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `call_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/transcript" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "object": "transcript", "meeting_id": "call_ExampleCall01", "available": false, "status": "processing", "variant": null, "utterances": [], "created_at": null, "updated_at": null }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped call transcript. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get the overall call evaluation Source: https://docs.salesbud.com.br/reference/calls/getoverallcallevaluation get {"/v1/calls/{call_id}/evaluations/overall"} Returns a single [evaluation object](/objects/evaluation/). ### Authorization Requires the `calls.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `call_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/evaluations/overall" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": null, "object": "overall_meeting_evaluation", "meeting_id": "call_ExampleCall01", "status": "not_started", "score": null, "justification": null, "created_at": null, "updated_at": null }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Overall evaluation state and result. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List completed calls Source: https://docs.salesbud.com.br/reference/calls/listcalls get {"/v1/calls"} Calls are the records captured from a VoIP integration, addressed by their own `call_` identifiers. They answer the same five routes as meetings and carry the same fields except `bot_history`: a VoIP capture has no recording bot, so the field is absent, not empty. Returns a list of [call objects](/objects/call/). ### Authorization Requires the `calls.read` scope. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | | `created_after` | `string date-time` | No | Return records created after this instant. | | `created_before` | `string date-time` | No | Return records created before this instant. | | `meeting_after` | `string date-time` | No | Return records whose event instant is after this value. | | `meeting_before` | `string date-time` | No | Return records whose event instant is before this value. | | `owner_email` | `string email` | No | Exact case-insensitive owner email match. | | `type` | `video` · `audio` | No | Media the record was captured as. Independent of the resource kind: a call can be video and a meeting can be audio. | | `audience` | `internal` · `external` | No | Whether the record had participants from outside the company (external) or only internal ones. | | `has_transcript` | `boolean` | No | Filter by existence of a transcript resource. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/calls?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "call_ExampleCall01", "object": "call", "title": "Call to +55 11 99999-0000", "status": "completed", "type": "audio", "audience": "external", "meeting_at": "2026-01-15T18:28:01.000Z", "duration_seconds": 714, "no_show": false, "participants": [ { "phone": "+5511999990000", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Wants a demo before renewing the current contract." } ] }, "tags": [], "customer_questions": [], "competitors": [], "enablement": { "next_steps": "Online meeting booked for Thursday at 13:30; send the invite today.", "meeting_type": null, "speaking_duration": { "users": [], "others_seconds": 677 } }, "accounts": [], "overall_evaluation": { "status": "not_started", "justification": null }, "skill_scores": [], "transcript": { "available": false, "status": "not_started" }, "created_at": "2026-01-15T19:22:08.000Z", "updated_at": "2026-01-15T19:22:53.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor02" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped call page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get the authenticated integration context Source: https://docs.salesbud.com.br/reference/context/getapicontext get {"/v1/context"} ### Authorization No authentication. ### Example request ```bash curl "https://api.salesbud.com.br/v1/context" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "object": "api_context", "client": { "id": "sb_client_ExampleClient01", "name": "acme-sync" }, "company": { "id": "cmp_ExampleCompany01", "name": "Example Company" }, "scopes": [ "meetings.read", "calls.read", "transcriptions.read" ], "rate_limit": { "requests_per_minute": 120 } }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | API client, company, scopes and rate-limit context. | | `401` | Missing or invalid credentials. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get an email conversation Source: https://docs.salesbud.com.br/reference/emails/getemail get {"/v1/emails/{email_id}"} Conversation metadata only. An `eml_` id issued to another company, or any id with a different prefix, is a 404, not a hint. ### Authorization Requires the `emails.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `email_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/emails/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": "eml_01JEXAMPLE", "object": "email_conversation", "subject": "Assunto de exemplo", "first_message_at": "2026-01-10T12:00:00.000Z", "last_message_at": "2026-01-12T15:30:00.000Z", "message_count": 3, "has_attachments": true, "last_message_direction": "inbound", "participants": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true }, { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false } ], "mailboxes": [ { "address": "vendedora@example.com", "owner": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" } }, { "address": "vendas@example.com", "owner": null } ], "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ] }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped email conversation. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List the messages of an email conversation Source: https://docs.salesbud.com.br/reference/emails/listemailmessages get {"/v1/emails/{email_id}/messages"} Messages in the order they were sent, oldest first, with plain-text bodies and attachment metadata (no attachment content). Governed by the `email_content` rate-limit policy in addition to the client and company policies. The cursor is bound to the conversation. ### Authorization Requires the `emails.read` and `emails.content.read` scopes together. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `email_id` | `string` | Yes | — | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/emails/mtg_01JEXAMPLE/messages?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "emsg_01JEXAMPLE", "object": "email_message", "conversation_id": "eml_01JEXAMPLE", "sent_at": "2026-01-12T15:30:00.000Z", "direction": "inbound", "subject": "Re: Assunto de exemplo", "snippet": "Trecho de exemplo da mensagem", "body_text": "Corpo de exemplo da mensagem.\n\nAtenciosamente,\nCliente Exemplo", "from": { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false }, "to": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true } ], "cc": [ { "address": "financeiro@example.net", "name": null, "internal": false } ], "attachments": [ { "filename": "exemplo.pdf", "mime_type": "application/pdf", "size_bytes": 182400 } ] } ], "pagination": { "limit": 50, "has_more": false, "next_cursor": null }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped email message page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List email conversations Source: https://docs.salesbud.com.br/reference/emails/listemails get {"/v1/emails"} Conversations are deduplicated across every mailbox connected to the company: two sellers on the same thread yield one conversation with two mailboxes. Ordered by `last_message_at` ascending (oldest activity first); pages forward with a signed keyset cursor. Message bodies live under `/v1/emails/{email_id}/messages` and need `emails.content.read`. ### Authorization Requires the `emails.read` scope. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | | `last_message_after` | `string date-time` | No | Return conversations whose latest message is after this instant. Filters on activity, not on record changes: a conversation that gains a message moves forward and will be seen again. | | `last_message_before` | `string date-time` | No | Return conversations whose latest message is before this instant. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/emails?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "eml_01JEXAMPLE", "object": "email_conversation", "subject": "Assunto de exemplo", "first_message_at": "2026-01-10T12:00:00.000Z", "last_message_at": "2026-01-12T15:30:00.000Z", "message_count": 3, "has_attachments": true, "last_message_direction": "inbound", "participants": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true }, { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false } ], "mailboxes": [ { "address": "vendedora@example.com", "owner": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" } }, { "address": "vendas@example.com", "owner": null } ], "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ] } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor03" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped email conversation page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get one completed meeting Source: https://docs.salesbud.com.br/reference/meetings/getmeeting get {"/v1/meetings/{meeting_id}"} Returns a single [meeting object](/objects/meeting/). ### Authorization Requires the `meetings.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `meeting_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": "mtg_ExampleMeeting01", "object": "meeting", "title": "Discovery - Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "no_show": false, "participants": [ { "display_name": "Example Buyer", "email": "buyer@example.com", "kind": "external" }, { "email": "procurement@example.com", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Manual CRM entry after every call, about two hours a day." } ] }, "tags": [ { "id": "tag_ExampleTag01", "name": "Enterprise" } ], "customer_questions": [ { "question": "Does it integrate with our CRM?", "category": "integrations" } ], "competitors": [ { "id": "ctr_ExampleCompetitor01", "name": "Example CRM" } ], "enablement": { "next_steps": "Send the proposal by Friday covering 5,400 seats.", "meeting_type": "qualification", "speaking_duration": { "users": [ { "user": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com" }, "duration_seconds": 1760 } ], "others_seconds": 1080 } }, "accounts": [ { "id": "acc_ExampleAccount01", "domain": "acme.example.com", "cnpj": "12345678000199" } ], "overall_evaluation": { "status": "completed", "justification": "Strong discovery, weak on next steps." }, "skill_scores": [], "bot_history": [], "transcript": { "available": true, "status": "completed" }, "created_at": "2026-01-14T12:58:47.000Z", "updated_at": "2026-01-14T14:10:43.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped meeting. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List the default template answers for a meeting Source: https://docs.salesbud.com.br/reference/meetings/getmeetinganswers get {"/v1/meetings/{meeting_id}/answers"} Returns a list of [answer objects](/objects/answer/). ### Authorization Requires the `meetings.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `meeting_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/answers" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "ans_ExampleAnswer01", "object": "meeting_answer", "question": { "id": "qst_ExampleQuestion01", "text": "What pains did the customer describe?", "type": "text", "order": 1 }, "answer": "Manual CRM entry after every call, about two hours a day.", "updated_at": "2026-01-14T14:10:43.000Z" } ], "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Answers generated for the meeting owner context. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get a meeting transcript Source: https://docs.salesbud.com.br/reference/meetings/getmeetingtranscript get {"/v1/meetings/{meeting_id}/transcript"} Returns the enhanced transcript when available, otherwise the original transcript. An unavailable transcript is represented by a successful response with available set to false and its current processing status. Returns a single [transcript object](/objects/transcript/). ### Authorization Requires the `meetings.read` and `transcriptions.read` scopes together. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `meeting_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/transcript" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "object": "transcript", "meeting_id": "mtg_ExampleMeeting01", "available": true, "status": "completed", "variant": "enhanced", "utterances": [ { "speaker": "Example Rep", "text": "Walk me through how the team logs a call today.", "start_ms": 12400, "end_ms": 16800 }, { "speaker": "buyer@example.com", "text": "Manually, right after. It costs us about two hours a day.", "start_ms": 17200, "end_ms": 23900 } ], "created_at": "2026-01-14T14:02:00.000Z", "updated_at": "2026-01-14T14:09:12.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped meeting transcript. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get the overall meeting evaluation Source: https://docs.salesbud.com.br/reference/meetings/getoverallmeetingevaluation get {"/v1/meetings/{meeting_id}/evaluations/overall"} Returns a single [evaluation object](/objects/evaluation/). ### Authorization Requires the `meetings.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `meeting_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/evaluations/overall" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": "evl_ExampleEvaluation01", "object": "overall_meeting_evaluation", "meeting_id": "mtg_ExampleMeeting01", "status": "completed", "score": 8, "justification": "Strong discovery, weak on next steps.", "created_at": "2026-01-14T14:05:00.000Z", "updated_at": "2026-01-14T14:10:43.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Overall evaluation state and result. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List completed meetings Source: https://docs.salesbud.com.br/reference/meetings/listmeetings get {"/v1/meetings"} Returns a list of [meeting objects](/objects/meeting/). ### Authorization Requires the `meetings.read` scope. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | | `created_after` | `string date-time` | No | Return records created after this instant. | | `created_before` | `string date-time` | No | Return records created before this instant. | | `meeting_after` | `string date-time` | No | Return records whose event instant is after this value. | | `meeting_before` | `string date-time` | No | Return records whose event instant is before this value. | | `owner_email` | `string email` | No | Exact case-insensitive owner email match. | | `type` | `video` · `audio` | No | Media the record was captured as. Independent of the resource kind: a call can be video and a meeting can be audio. | | `audience` | `internal` · `external` | No | Whether the record had participants from outside the company (external) or only internal ones. | | `has_transcript` | `boolean` | No | Filter by existence of a transcript resource. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/meetings?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "mtg_ExampleMeeting01", "object": "meeting", "title": "Discovery - Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "no_show": false, "participants": [ { "display_name": "Example Buyer", "email": "buyer@example.com", "kind": "external" }, { "email": "procurement@example.com", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Manual CRM entry after every call, about two hours a day." } ] }, "tags": [ { "id": "tag_ExampleTag01", "name": "Enterprise" } ], "customer_questions": [ { "question": "Does it integrate with our CRM?", "category": "integrations" } ], "competitors": [ { "id": "ctr_ExampleCompetitor01", "name": "Example CRM" } ], "enablement": { "next_steps": "Send the proposal by Friday covering 5,400 seats.", "meeting_type": "qualification", "speaking_duration": { "users": [ { "user": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com" }, "duration_seconds": 1760 } ], "others_seconds": 1080 } }, "accounts": [ { "id": "acc_ExampleAccount01", "domain": "acme.example.com", "cnpj": "12345678000199" } ], "overall_evaluation": { "status": "completed", "justification": "Strong discovery, weak on next steps." }, "skill_scores": [], "bot_history": [], "transcript": { "available": true, "status": "completed" }, "created_at": "2026-01-14T12:58:47.000Z", "updated_at": "2026-01-14T14:10:43.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor01" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped meeting page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Get a WhatsApp conversation Source: https://docs.salesbud.com.br/reference/whatsapp/getwhatsappconversation get {"/v1/whatsapp/{whatsapp_id}"} Conversation metadata only: contact, seller, linked accounts and activity instants. A `wa_` id issued to another company, or any id with a different prefix, is a 404, not a hint. A deleted chat keeps answering, with `deleted_at` and `deleted_reason` set. ### Authorization Requires the `whatsapp.read` scope. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `whatsapp_id` | `string` | Yes | — | ### Example request ```bash curl "https://api.salesbud.com.br/v1/whatsapp/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": { "id": "wa_01JEXAMPLE", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": false, "contact": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ], "first_message_at": "2026-09-10T12:00:00.000Z", "last_message_at": "2026-09-12T15:30:00.000Z", "last_message_direction": "inbound", "deleted_at": null, "deleted_reason": null }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped WhatsApp conversation. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List WhatsApp conversations Source: https://docs.salesbud.com.br/reference/whatsapp/listwhatsappconversations get {"/v1/whatsapp"} One conversation per chat of a company seller, from WhatsApp Web or RD Conversas. Ordered by `last_message_at` ascending (oldest activity first); pages forward with a signed keyset cursor. `last_message_at` moves only when a message arrives: an edit or a deletion does not move the conversation, so re-read messages to observe them. Message content lives under `/v1/whatsapp/{whatsapp_id}/messages` and needs `whatsapp.content.read`. ### Authorization Requires the `whatsapp.read` scope. ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | | `last_message_after` | `string date-time` | No | Return conversations whose latest message is after this instant. Filters on activity, not on record changes: a conversation that gains a message moves forward and will be seen again. | | `last_message_before` | `string date-time` | No | Return conversations whose latest message is before this instant. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/whatsapp?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "wa_01JEXAMPLE", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": false, "contact": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ], "first_message_at": "2026-09-10T12:00:00.000Z", "last_message_at": "2026-09-12T15:30:00.000Z", "last_message_direction": "inbound", "deleted_at": null, "deleted_reason": null }, { "id": "wa_01JGROUP", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": true, "contact": { "phone": null, "name": "Grupo Vendas Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [], "first_message_at": "2026-09-11T09:00:00.000Z", "last_message_at": "2026-09-12T18:00:00.000Z", "last_message_direction": "outbound", "deleted_at": null, "deleted_reason": null } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor04" }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped WhatsApp conversation page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # List the messages of a WhatsApp conversation Source: https://docs.salesbud.com.br/reference/whatsapp/listwhatsappmessages get {"/v1/whatsapp/{whatsapp_id}/messages"} Messages in the order they were sent, oldest first, with sanitized text, media metadata (no media content) and the audio transcription when one exists. A message deleted for everyone stays in place as a tombstone with `deleted_at` set and every content field null; an edited message carries its current text and `edited_at`. Governed by the `whatsapp_content` rate-limit policy in addition to the client and company policies. The cursor is a keyset over `(sent_at, id)` bound to the conversation. ### Authorization Requires the `whatsapp.read` and `whatsapp.content.read` scopes together. ### Path parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `whatsapp_id` | `string` | Yes | — | ### Query parameters | Name | Type | Required | Description | | --- | --- | --- | --- | | `limit` | `integer` | No | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | No | Signed cursor returned by the previous page. | ### Example request ```bash curl "https://api.salesbud.com.br/v1/whatsapp/mtg_01JEXAMPLE/messages?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Example response ```json title="200 OK" { "data": [ { "id": "wamsg_01JEXAMPLE1", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:30:00.000Z", "direction": "inbound", "author": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "type": "text", "text": "Pode me mandar a proposta?", "media": null, "transcription": null, "edited_at": null, "deleted_at": null, "deleted_reason": null }, { "id": "wamsg_01JEXAMPLE2", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:32:00.000Z", "direction": "outbound", "author": { "phone": "+5511988880001", "name": "Vendedora Exemplo" }, "type": "audio", "text": null, "media": { "file_name": null, "mime_type": "audio/ogg; codecs=opus", "size_bytes": 48213 }, "transcription": "Claro, envio ainda hoje por e-mail.", "edited_at": null, "deleted_at": null, "deleted_reason": null }, { "id": "wamsg_01JEXAMPLE3", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:35:00.000Z", "direction": "inbound", "author": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "type": "text", "text": null, "media": null, "transcription": null, "edited_at": null, "deleted_at": "2026-09-12T15:36:00.000Z", "deleted_reason": "revoked_for_everyone" } ], "pagination": { "limit": 50, "has_more": false, "next_cursor": null }, "request_id": "req_ExampleRequestId01" } ``` ### Responses | Status | Description | | --- | --- | | `200` | Company-scoped WhatsApp message page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note This page is generated from the OpenAPI specification. Do not edit it by hand. ::: --- # Changelog Source: https://docs.salesbud.com.br/resources/changelog Breaking changes to a released version are announced before they ship. Additive changes — a new field, a new endpoint, a new error code — can arrive at any time, so parse defensively: ignore fields you do not recognise. ## Unreleased — first delivery Version `v1` has not been released to partners yet. Everything below is the shape of the first delivery. **Tooling** - An official MCP server ships alongside v1: `@salesbud/mcp` on npm, one tool per public read operation, for Claude Desktop, Claude Code and Cursor. It is a client of this API and adds no capability of its own. See [MCP server](/guides/mcp/). **Resources** - Calls are their own collection at `/v1/calls`, with `call_` identifiers. A record captured from a VoIP integration has `object: "call"`; everything else is a `meeting`. Ids do not resolve across collections. - All five routes are mirrored for both collections: list, retrieve, transcript, template answers and overall evaluation. - A call does not carry `bot_history` at all: a VoIP capture has no recording bot, so the field is omitted rather than returned empty, and a schema that validates a call rejects it. `enablement.meeting_type` stays and is often `null` — that null is legitimate, unlike the bot. - WhatsApp conversations of the company's sellers are a collection at `/v1/whatsapp`, with `wa_` identifiers and `wamsg_` messages, behind `whatsapp.read` (metadata) and `whatsapp.content.read` (sanitized text, media metadata, audio transcriptions). `last_message_at` moves only on a new message, so re-read messages to observe edits and deletions; deleted messages stay as tombstones. Date filters compare in whole seconds. See [WhatsApp](/reference/whatsapp/listwhatsappconversations/). **Errors** - The error envelope is `{ type, title, detail, code, request_id }`. Every field is snake_case; the HTTP status stays in the status line and is not repeated in the body. Branch on `code`. - `POST /oauth/token` keeps the OAuth 2.0 error shape required by RFC 6749 §5.2. **Pagination and filters** - Cursors are signed and ordered by ascending id; they bind the filters of the request that produced them. - `updated_after`, `updated_before` and `snapshot_at` were removed. Without a dedicated visibility timestamp there is no honest incremental sync; re-query by period instead. `updated_at` remains in responses as informative only. - The date filters are `meeting_after` and `meeting_before`. Earlier drafts also accepted `meeting_at_from` and `meeting_at_to`; those never reached a partner and were removed rather than shipped as deprecated on day one. **Access** - The API is gated by the `API_ACCESS` company feature, checked at token issuance and at credential creation. - Emails and account domains are normalised to lower case in every field, so an address can be used as a join key regardless of where it appears. --- # For LLMs and agents Source: https://docs.salesbud.com.br/resources/for-agents This documentation is published in machine-readable form as well as HTML, so an agent can consume it without scraping markup. ## Entry points | File | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index: one line per page, with its URL and a one-line summary. Start here. | | [`/llms-full.txt`](/llms-full.txt) | Every page concatenated as plain Markdown, in reading order. | | [`/openapi.yaml`](/openapi.yaml) | The normative contract. Everything in the API reference is generated from it. | The format follows the [llms.txt convention](https://llmstxt.org/). ## Any page as Markdown Append `.md` to any documentation URL to get its source instead of the rendered page: ``` https://docs.salesbud.com.br/guides/pagination/ → HTML https://docs.salesbud.com.br/guides/pagination.md → Markdown ``` Both language versions are available; the pt-BR pages live under `/pt-br/`. ## If you only need to read data from an agent This API serves an MCP server at `https://api.salesbud.com.br/mcp`, which claude.ai reaches as a custom connector. It exposes every public read operation as a tool, and handles token renewal, retries and transcript windowing for you. That is the shortest path from a credential to an agent reading meetings — see [MCP server](/guides/mcp/). Write an HTTP client instead when you are building a service rather than driving a model. ## If you are generating integration code Three things are easy to get wrong and worth reading before you write anything: 1. **Loop on `pagination.has_more`, never on `data.length`.** A page can be short or empty while more data remains. See [Pagination](/guides/pagination/). 2. **Branch on `error.code`, never on `error.detail`.** The detail is human text and may be reworded. See [Errors](/guides/errors/). 3. **Do not infer the resource kind from `type`.** `object` says whether it is a `meeting` or a `call`; `type` only says `video` or `audio`, and the two are independent. See [Meetings and calls](/guides/meetings-and-calls/). There is no refresh token — renewal is calling `/oauth/token` again. That is a property of the client-credentials grant, not an omission. --- # API Salesbud Source: https://docs.salesbud.com.br/pt-br Reunião gravada pelo bot e ligação capturada por VoIP são recursos diferentes, então vivem em caminhos diferentes: `/v1/meetings` e `/v1/calls`. Mesma forma, identificadores próprios. A credencial pertence a uma empresa, nunca a um usuário. Toda leitura é filtrada por ela — não existe parâmetro que amplie esse escopo. Recursos são endereçados por ids opacos com prefixo (`mtg_`, `call_`, `usr_`). Ids internos sequenciais nunca são expostos. Toda página existe em Markdown puro, além de [`llms.txt`](/llms.txt) e [`llms-full.txt`](/llms-full.txt) para ferramentas que leem documentação diretamente. ## Sua primeira requisição Troque a credencial por um token de acesso e leia uma página de reuniões. ```bash title="Primeira requisição" # 1. Obtenha um token de acesso (vale 1 hora) 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" # 2. Leia as reuniões concluídas de um período curl "https://api.salesbud.com.br/v1/meetings?meeting_after=2026-01-01T00:00:00Z&limit=50" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` A resposta é uma página de reuniões mais um cursor: ```json title="200 OK" { "data": [ { "id": "mtg_H9_chV2YG6UGE0n31AqvDQ", "object": "meeting", "title": "Discovery — Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "participants": [{ "email": "comprador@acme.com", "kind": "external" }], "owner": { "id": "usr_CKAyHd20jJb6GGJqhNF9vA", "email": "vendedor@suaempresa.com" }, "transcript": { "available": true, "status": "completed" } } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_..." } } ``` ## Por onde seguir | Se você quer… | Leia | | --- | --- | | Fazer a primeira chamada de ponta a ponta | [Começo rápido](/pt-br/get-started/quickstart/) | | Entender credenciais, tokens e escopos | [Autenticação](/pt-br/guides/authentication/) | | Saber quando um registro é `meeting` e quando é `call` | [Reuniões e ligações](/pt-br/guides/meetings-and-calls/) | | Percorrer todo o histórico com segurança | [Paginação](/pt-br/guides/pagination/) | | Tratar falhas corretamente | [Erros](/pt-br/guides/errors/) | | Ver cada endpoint, campo e status | [Referência da API](/pt-br/reference/meetings/listmeetings/) | :::note[O acesso é liberado por empresa] A API é gateada pela feature `API_ACCESS` da empresa. Se a sua não tiver a feature ligada, a emissão de token falha com `403 API_ACCESS_DISABLED` — fale com seu contato na Salesbud em vez de repetir a chamada. ::: --- # O objeto resposta Source: https://docs.salesbud.com.br/pt-br/objects/answer Devolvido pelas rotas de respostas de template, um item por pergunta do template padrão do registro.
id string
Opaque answer identifier.
object meeting_answer
Resource type. Always meeting_answer, including for calls.
question object
The question this answers.
question.id string
Opaque question identifier, stable across records that share the template. It is the field to key on, not the text, which the company can reword.
question.text string
Question text as the template asks it, sanitized.
question.type text · option · multi_select · boolean · number
What the template expects as an answer. The answer itself always arrives as a string, so this is what says how to parse it.
question.order integer
Position of the question in the template, starting at zero.
answer string
Generated answer. A question that produced none is left out of the list rather than returned with a null answer.
updated_at string date-time
When the answer last changed.
:::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # O objeto ligação Source: https://docs.salesbud.com.br/pt-br/objects/call Devolvido por todas as rotas de `/v1/calls`. A ligação responde às mesmas cinco rotas de uma reunião e carrega os mesmos campos, exceto `bot_history`; o resto do que muda é quais registros a coleção guarda e como eles costumam vir. Todo campo está documentado em [o objeto reunião](/pt-br/objects/meeting/). O que segue é só o que difere. ## O que difere
object
**`call`, nunca `meeting`.** É o campo que diz qual recurso você tem em mãos.
id
**Prefixo `call_`.** Um id `mtg_` não resolve em /v1/calls, e o contrário também é 404.
participants
**Em geral um item só, com `phone`.** O registro de VoIP identifica a outra ponta pelo número, não por e-mail.
type
**Em geral `audio`, mas não sempre.** Existem ligações em vídeo; quem distingue o recurso é `object`, nunca `type`.
enablement.meeting_type
**Costuma vir `null`.** O classificador foi feito para reunião de venda e não rotula ligação.
:::note[Campos que a ligação não tem] `bot_history` não está neste objeto: captura de VoIP não tem bot de gravação, então o campo nunca se aplica e a resposta o omite em vez de mandar array vazio. Um schema que valida ligação recusa o campo. `enablement.meeting_type` fica, e costuma vir `null`. Esse nulo é legítimo — o classificador de estágio de venda não rotula ligação hoje, mas poderia. Não é o mesmo caso do bot. ::: :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # O objeto avaliação Source: https://docs.salesbud.com.br/pt-br/objects/evaluation Devolvido pelas rotas de avaliação geral. Todo campo além de `object` e `status` é nulo até a avaliação concluir.
id string aceita null
Opaque evaluation ID, null while no evaluation exists.
object overall_meeting_evaluation
Resource type. Always overall_meeting_evaluation, including for calls.
meeting_id string
The record this evaluation belongs to. Carries the prefix of its collection: mtg_ or call_.
status not_started · processing · completed · failed
Public processing state.
score integer aceita null
Score from 0 to 10, or null until the evaluation completes.
justification string aceita null
Why the score was given, or null until the evaluation completes.
created_at string date-time aceita null
When the evaluation was created, or null when none exists yet.
updated_at string date-time aceita null
When the evaluation last changed, or null when none exists yet.
:::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # O objeto reunião Source: https://docs.salesbud.com.br/pt-br/objects/meeting Devolvido por todas as rotas de `/v1/meetings`. A ligação carrega os mesmos campos, exceto `bot_history` — veja [o objeto ligação](/pt-br/objects/call/) para o que muda.
id string
Always prefixed `mtg_`. A `call_` id does not address a meeting: the API refuses it rather than resolving it in the other collection.
object meeting
Resource type. A record captured from a VoIP integration is a `call`; everything else is a `meeting`. Independent of `type`, which describes the media: a call can be video and a meeting can be audio.
title string
Sanitized meeting title.
status completed
Only semantically completed meetings are public in V1.
type video · audio
Meeting media type.
audience internal · external
Meeting audience classification.
meeting_at string date-time
Meeting instant in UTC.
duration_seconds integer
Non-negative meeting duration.
no_show boolean
Whether the completed meeting had no attendee.
participants array of object
Normalized external participants.
owner object
The user the record belongs to. Company scoping follows this user's default company.
template object aceita null
Public meeting template reference when available.
tags array of object
Public tag references.
customer_questions array of object
Customer questions detected in the call.
competitors array of object
Competitors mentioned in the call.
enablement object aceita null
Enablement summary when available.
accounts array of object
Company-scoped accounts linked to the meeting.
overall_evaluation object
Overall evaluation readiness and score summary.
skill_scores array of object
Per-skill scores; empty when no skill evaluation exists.
transcript object
Transcript availability and its consistent processing state.
created_at string date-time
UTC instant when the meeting entered Salesbud.
updated_at string date-time
UTC instant of the last change to the meeting record itself. Informational only: it does not advance when a dependent resource such as a transcript, tag or answer changes without touching the meeting row, so it is not a reliable incremental-sync marker.
bot_history array of object
Recording bot events. Present only on a meeting: a VoIP capture has no bot, so a call does not carry this field at all.
## Objetos aninhados ### Meeting participant
display_name string opcional
Sanitized external participant name when available.
email string email opcional
Normalized lowercase email when available.
phone string opcional
Normalized phone number when available.
kind external
Participant classification in V1.
### Meeting owner
id string
Opaque owner identifier.
name string
Public owner display name.
email string email aceita null
Normalized owner email, or null when legacy data is malformed.
team object aceita null
Owner default team inside the authenticated company.
### Meeting owner team
id string
Opaque team identifier.
name string
Team display name.
role participant · leadership · observer
Role of the owner in the team. `participant` sees only their own meetings; `leadership` sees the team's and is visible to it; `observer` sees the team's without appearing to the other members.
is_default boolean
Whether this is the owner's default team in the company. False when the owner has no default team there and the API reported another one instead, which is why the field exists rather than being implied.
### Template reference
id string
Opaque public template identifier.
name string
Public display name.
answers array of object
Sanitized template questions and meeting answers.
### Meeting template answer
field string
Opaque public template question field identifier.
question string
Question text as it was asked, sanitized.
answer string
Answer generated for this question. A question that produced none is left out of the list rather than returned with a null answer.
### Tag reference
id string
Opaque public tag identifier.
name string
Public display name.
### Meeting customer question
question string
Question the customer asked during the conversation.
category string
Category the question was classified into.
### Meeting competitor
id string
Opaque competitor identifier.
name string
Competitor name as registered by the company, not as spoken.
### Meeting enablement
next_steps string aceita null
Next steps agreed in the conversation, or null when none were identified.
meeting_type qualification · negotiation · proposal · closing · technical_meeting · questions_clarifications aceita null
Sales stage the conversation was classified as, or null. Often null on calls: the classifier was built for sales meetings.
speaking_duration object
How long each side spoke.
speaking_duration.users array of object
One entry per company user who spoke. A user who did not speak is left out rather than reported with zero.
speaking_duration.users[].user object
A company user as the API exposes them anywhere other than `owner`: the same identifier, without the team.
speaking_duration.users[].duration_seconds integer
How long this user spoke, in seconds.
speaking_duration.others_seconds integer aceita null
How long everyone outside the company spoke, in seconds, or null when the split was not measured. Not broken down per person: the other side is not identified by user.
### Public user reference A company user as the API exposes them anywhere other than `owner`: the same identifier, without the team.
id string
Opaque user identifier, the same one `owner.id` carries.
name string
Public user display name.
email string email aceita null
Normalized lowercase user email, or null when legacy data is malformed.
### Account reference
id string
Opaque account identifier.
domain string
Account email domain, lowercased.
cnpj string aceita null
Brazilian company registration number as stored, with or without punctuation, or null when unknown.
### Meeting skill score
user object
The user the score refers to.
skill object
The skill that was scored.
skill.id string
Opaque skill identifier.
skill.name string
Skill name as the company registered it.
score integer
Score from 0 to 10.
justification string aceita null
Why the score was given, or null when the evaluation produced none.
### Meeting bot event
code string aceita null
What happened to the recording bot, as a stable code.
subcode string aceita null
Further detail on the event, or null when the code carries none.
occurred_at string date-time
When the event happened.
:::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # O objeto paginação Source: https://docs.salesbud.com.br/pt-br/objects/pagination Acompanha toda resposta de listagem. Itere por `has_more`, nunca pelo tamanho de `data` — veja [Paginação](/pt-br/guides/pagination/).
limit integer
Number of resources requested.
has_more boolean
Whether another page is available. A page can be short or empty while this is true, so always continue while it is true instead of relying on the size of data.
next_cursor string aceita null
Signed opaque cursor for the next page.
:::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # O objeto transcrição Source: https://docs.salesbud.com.br/pt-br/objects/transcript Devolvido pelas rotas de transcrição. Transcrição indisponível é resposta bem-sucedida com `available: false`, não erro.
object transcript
Resource type. Always `transcript`, including for a call.
meeting_id string
The record this transcript belongs to. Carries the prefix of its collection: `mtg_` or `call_`.
available true · false
Whether there is a transcript to read. `false` is a successful response carrying the reason in `status`, not an error.
status completed · not_started · processing · failed
Processing state. Always `completed` while `available` is true; otherwise why the transcript is not there yet.
variant enhanced · original aceita null
Which transcript was returned: `enhanced` when the improved version exists, `original` otherwise. Null while none is available.
utterances array of object
The transcript itself, in chronological order. Always empty while the transcript is unavailable.
created_at string date-time aceita null
When the transcript was first stored, or null when none exists yet.
updated_at string date-time aceita null
When the transcript last changed, or null when none exists yet.
## Objetos aninhados ### Meeting transcript utterance
speaker string aceita null
Sanitized speaker label when available.
text string
Sanitized utterance text.
start_ms integer
Utterance start offset in milliseconds.
end_ms integer
Utterance end offset in milliseconds.
:::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Começo rápido Source: https://docs.salesbud.com.br/pt-br/get-started/quickstart Este guia vai de uma credencial nova até uma página de reuniões. São duas requisições. ## Antes de começar Você precisa de um **client ID** e um **client secret**, emitidos pela Salesbud para a sua empresa. A credencial não é self-service: peça ao seu contato na Salesbud, que também confirma se a feature `API_ACCESS` está ligada. O secret aparece uma única vez, na criação. Guarde num gerenciador de segredos — não dá para lê-lo depois, só rotacionar. 1. **Obtenha um token de acesso** Os tokens são emitidos por OAuth 2.0 client credentials. Envie a credencial como campos form-encoded: ```bash 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" ``` ```js 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(); ``` ```python import os, requests response = 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"] ``` O endpoint aceita apenas `application/x-www-form-urlencoded`. Mandar JSON devolve `415 UNSUPPORTED_MEDIA_TYPE`. ```json title="200 OK" { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "scope": "meetings.read calls.read" } ``` 2. **Confirme o que o token pode fazer** `GET /v1/context` informa a empresa, os escopos e o limite de requisições em vigor para aquele token. É o jeito mais rápido de separar problema de configuração de bug de integração. ```bash curl https://api.salesbud.com.br/v1/context \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` 3. **Leia uma página de reuniões** ```bash curl "https://api.salesbud.com.br/v1/meetings?meeting_after=2026-01-01T00:00:00Z&limit=50" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` Só voltam reuniões da sua empresa que terminaram o processamento. Veja [Reuniões e ligações](/pt-br/guides/meetings-and-calls/) para entender o que é "terminou" e por que os registros de VoIP ficam em `/v1/calls`. 4. **Percorra o resto do histórico** Devolva o `next_cursor` sem alterar, mantendo todos os filtros idênticos, e itere pelo `has_more` — nunca pelo tamanho de `data`. ```js 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); ``` Uma página pode voltar curta — até vazia — com `has_more` em `true`. Isso é esperado: o serviço limita quanto varre por requisição. A [Paginação](/pt-br/guides/pagination/) explica as garantias. ## Renovando o token Não existe refresh token. Client credentials é um fluxo máquina-a-máquina, sem usuário para reconsentir, então a [RFC 6749 §4.4.3](https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3) diz para não emitir um. Quando o token expira, chame `/oauth/token` de novo com a mesma credencial. Na prática: guarde o token por um pouco menos que o `expires_in` e reemita também em `401 INVALID_ACCESS_TOKEN`, não só por relógio — uma credencial pode ser revogada antes de o prazo acabar. ## Se quem lê é um modelo Se o consumidor é um agente e não um serviço, dá para pular o cliente HTTP: o [servidor MCP](/pt-br/guides/mcp/) expõe estas mesmas operações como tools para Claude Desktop, Claude Code e Cursor, e cuida da renovação de token e dos retries. A credencial acima continua sendo necessária. ## Falhas comuns na primeira chamada | Resposta | O que significa | | --- | --- | | `415 UNSUPPORTED_MEDIA_TYPE` | O pedido de token foi enviado como JSON. Use form encoding. | | `401 INVALID_CLIENT` | Client ID ou secret errado, ou credencial revogada. | | `403 API_ACCESS_DISABLED` | A empresa não tem a feature `API_ACCESS`. Repetir não resolve. | | `403 INSUFFICIENT_SCOPE` | Falta um escopo que a rota exige — `calls.read` para ligações, e `transcriptions.read` somado ao escopo da coleção para transcrição. | | `404 RESOURCE_NOT_FOUND` | O id não existe, é de outra empresa, ou é da outra coleção. | --- # Autenticação Source: https://docs.salesbud.com.br/pt-br/guides/authentication Toda requisição carrega um bearer token, obtido trocando uma credencial em `POST /oauth/token`. ## A credencial é uma empresa, não um usuário A credencial identifica **a sua empresa**. Ela não está ligada a uma pessoa, um login, um time ou um workspace selecionado, e não herda a permissão de ninguém. Tudo que o token lê tem o escopo da empresa para a qual foi emitido, e nenhum parâmetro amplia isso. É por isso que não existe fluxo de login de usuário: não há usuário. ## Obtendo um token ```bash 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" ``` O endpoint aceita apenas `application/x-www-form-urlencoded`. A credencial também pode ir por HTTP Basic em vez de no corpo. ```json title="200 OK" { "access_token": "eyJhbGciOiJSUzI1NiIs...", "token_type": "Bearer", "expires_in": 3600, "scope": "meetings.read calls.read transcriptions.read" } ``` Os tokens são assinados em RS256 e carregam a empresa, os escopos concedidos e a versão da credencial. ## Escopos | Escopo | Permite | | --- | --- | | `meetings.read` | Listar e obter reuniões, suas respostas de template e sua avaliação geral. | | `calls.read` | Listar e obter ligações, suas respostas de template e sua avaliação geral. | | `transcriptions.read` | Ler transcrições, junto com o escopo da coleção a que a transcrição pertence. Pedido à parte porque a transcrição é o dado mais sensível que a API serve. | A transcrição exige dois escopos: `meetings.read` e `transcriptions.read` para a transcrição de uma reunião, `calls.read` e `transcriptions.read` para a de uma ligação. `transcriptions.read` sozinho não lê nada. O token recebe os escopos anexados à credencial. Chamar uma rota fora deles devolve `403 INSUFFICIENT_SCOPE` — é o portão funcionando, não rota faltando. Use `GET /v1/context` para ver os escopos que um token realmente carrega. ## Tempo de vida e renovação Os tokens expiram depois de `expires_in` segundos (3600 por padrão). **Não existe refresh token, por desenho.** Client credentials é um fluxo máquina-a-máquina, sem usuário para reconsentir, então a [RFC 6749 §4.4.3](https://www.rfc-editor.org/rfc/rfc6749#section-4.4.3) diz para não emitir um — seria um segundo segredo de vida longa com o mesmo poder do primeiro. Renovar é chamar `/oauth/token` de novo. Guarde o token um pouco abaixo do `expires_in` e reemita também quando uma requisição devolver `401 INVALID_ACCESS_TOKEN`. Uma credencial pode ser revogada antes de o prazo acabar; só o relógio não percebe. ## Rotacionando um secret A rotação emite um secret novo e mantém o anterior válido por **sete dias**, para você fazer deploy do valor novo sem janela de erro. No máximo dois secrets ficam válidos ao mesmo tempo. Revogue o que está expirando antes de iniciar outra rotação, senão a segunda é recusada com `409 ROTATION_IN_PROGRESS`. Revogar ou desabilitar o client inteiro invalida os tokens emitidos na hora — é essa a alavanca em caso de suspeita de vazamento, não a rotação. ## Allowlist de IP A credencial pode ter uma lista opcional de CIDRs IPv4/IPv6. Quando definida, ela é aplicada tanto na emissão do token quanto em toda requisição que o usa, então um token vazado não serve fora da sua rede. :::caution[O secret aparece uma vez] Ele é guardado só como hash HMAC-SHA256 com pepper do serviço e não pode ser lido de volta. Se for perdido, rotacione — não há caminho de recuperação. ::: --- # Erros Source: https://docs.salesbud.com.br/pt-br/guides/errors Os erros usam um envelope só, em toda a API: ```json { "error": { "type": "ForbiddenError", "title": "Forbidden", "detail": "The transcriptions.read scope is required.", "code": "INSUFFICIENT_SCOPE", "request_id": "req_9bfbe80ac1f24e0a9d5b1e5e7a0f2c11" } } ``` ## Ramifique pelo `code` O `code` é o campo estável, legível por máquina. Ele não muda com redação, tradução ou refatoração. `title` e `detail` são texto humano e **podem ser reescritos** entre versões. O `type` é a classe do erro e é estável, mas mais grosseiro que o `code` — vários códigos compartilham uma classe. Nunca faça parse do `detail`. Cite o `request_id` ao acionar o suporte: ele identifica a requisição exata no nosso log. ## Códigos | HTTP | `code` | Significado | | --- | --- | --- | | 400 | `INVALID_LIMIT` | Tamanho de página fora de 1–100. | | 400 | `INVALID_DATETIME` | Um carimbo de tempo não é RFC 3339 com offset explícito. | | 400 | `INVALID_FILTER` | Valor ou combinação de filtro não suportada. | | 400 | `INVALID_CURSOR` | O cursor foi editado, ou os filtros mudaram no meio da travessia. | | 400 | `INVALID_REQUEST` | Falha de validação, incluindo parâmetro de query desconhecido. | | 401 | `MISSING_ACCESS_TOKEN` | Nenhum bearer token foi enviado. | | 401 | `INVALID_ACCESS_TOKEN` | O token é inválido, expirou, ou a credencial foi revogada. | | 403 | `INSUFFICIENT_SCOPE` | O token não carrega o escopo que a rota exige. | | 403 | `API_ACCESS_DISABLED` | A empresa não tem acesso à API habilitado. | | 404 | `RESOURCE_NOT_FOUND` | Não existe, é de outra empresa, ou foi pedido na coleção errada. | | 413 | `PAYLOAD_TOO_LARGE` | O corpo da requisição excede o tamanho aceito. | | 415 | `UNSUPPORTED_MEDIA_TYPE` | Content type errado — em geral JSON enviado ao `/oauth/token`. | | 429 | `RATE_LIMIT_EXCEEDED` | Uma política de rate limit recusou a requisição. | | 503 | `SERVICE_UNAVAILABLE` | Dependência fail-closed indisponível. Repita com backoff. | | 503 | `AUDIT_UNAVAILABLE` | Uma leitura bem-sucedida não pôde ser gravada na trilha de auditoria, então não foi devolvida. | ## Identificador desconhecido é sempre 404 Um id de outra empresa devolve `404 RESOURCE_NOT_FOUND`, não `403`. A API não confirma que o recurso existe em outro lugar — um `403` entregaria esse fato a quem estivesse sondando ids. ## O endpoint de token é diferente `POST /oauth/token` devolve o formato de erro do OAuth 2.0 exigido pela [RFC 6749 §5.2](https://www.rfc-editor.org/rfc/rfc6749#section-5.2), para que clientes OAuth de mercado continuem funcionando: ```json { "error": "invalid_client", "error_description": "Client authentication failed." } ``` ## O que nunca aparece num erro Stack trace, caminho de arquivo, token, secret, corpo da requisição e payload de domínio nunca são devolvidos. Uma falha inesperada devolve mensagem genérica fixa com um `request_id`; o detalhe fica no nosso log. ## Quando repetir | Status | Repetir? | | --- | --- | | `400`, `403`, `404`, `413`, `415` | Não. A requisição vai falhar igual. | | `401` | Uma vez, depois de reemitir o token. | | `429` | Sim, depois do `Retry-After`. | | `503` | Sim, com backoff exponencial e jitter. | --- # Servidor MCP Source: https://docs.salesbud.com.br/pt-br/guides/mcp Esta API serve um servidor [Model Context Protocol](https://modelcontextprotocol.io) por HTTP em `https://api.salesbud.com.br/mcp`. Cadastrado uma vez como conector personalizado no claude.ai, ele deixa o modelo ler reuniões, ligações, transcrições, respostas do template, avaliações, e-mails e conversas de WhatsApp com a sua credencial — sem nada para instalar. Ele é um cliente da API pública, não uma superfície nova: mesma credencial OAuth, mesmos escopos, mesmos limites de uso e **apenas operações de leitura** — nada que ele alcance é diferente do que a API HTTP devolveria para a mesma credencial. ## Antes de começar Você precisa de uma credencial de API com os escopos que pretende usar, da feature `API_ACCESS` habilitada para a sua empresa e de acesso de administrador na organização do Claude que vai guardar o conector. As credenciais não são self-service; veja o [Começo rápido](/pt-br/get-started/quickstart/). ## Cadastrar o conector Quem administra a organização no Claude cadastra um conector personalizado apontando para `https://api.salesbud.com.br/mcp`. A autenticação é **No sign-in** — este servidor não tem fluxo OAuth — e a credencial da empresa cujos dados o conector deve ler viaja em dois headers de requisição: | Header | Valor | | ----------------- | --------------- | | `X-Client-Id` | `sb_client_...` | | `X-Client-Secret` | `sb_secret_...` | Qualquer outro cliente HTTP pode mandar a mesma credencial como Basic (RFC 7617): ``` Authorization: Basic ``` Duas consequências de uma credencial cadastrada uma vez pelo administrador: - **Ela fica guardada do lado da Anthropic e é compartilhada pela organização inteira no Claude.** Todo mundo ali lê o que os escopos permitirem, e a trilha de auditoria registra a credencial, não quem pediu. - **Revogar significa rotacionar.** Rotacionar o segredo quebra junto qualquer outra integração que use a mesma credencial, então dê uma só para o conector. Se a credencial restringe acesso por IP, a allowlist precisa conter a faixa de saída da Anthropic, `160.79.104.0/21`: a requisição chega nesta API de lá, não do navegador do usuário. ## Tools Uma tool por operação pública de leitura. A coluna de escopo é o que a credencial precisa carregar — escopo faltando volta como uma mensagem legível de `INSUFFICIENT_SCOPE`, que o modelo consegue relatar, não como exceção. | Tool | Escopo | | -------------------------------- | ----------------------------------------- | | `get_api_context` | nenhum além de uma credencial válida | | `list_meetings` | `meetings.read` | | `get_meeting` | `meetings.read` | | `get_meeting_overall_evaluation` | `meetings.read` | | `get_meeting_answers` | `meetings.read` | | `get_meeting_transcript` | `meetings.read` + `transcriptions.read` | | `list_calls` | `calls.read` | | `get_call` | `calls.read` | | `get_call_overall_evaluation` | `calls.read` | | `get_call_answers` | `calls.read` | | `get_call_transcript` | `calls.read` + `transcriptions.read` | | `list_emails` | `emails.read` | | `get_email` | `emails.read` | | `list_email_messages` | `emails.read` + `emails.content.read` | | `list_whatsapp_conversations` | `whatsapp.read` | | `get_whatsapp_conversation` | `whatsapp.read` | | `list_whatsapp_messages` | `whatsapp.read` + `whatsapp.content.read` | Peça `get_api_context` primeiro quando não souber o que a credencial alcança: ele responde com o client, a empresa, os escopos e o limite por minuto. O `/oauth/token` não é tool de propósito — o servidor emite e renova o token por dentro, e expor isso gastaria o bucket de OAuth e colocaria o token na conversa. As sondas de liveness e readiness também não são tools. ## Transcrições vêm em janelas Uma transcrição de uma hora tem milhares de falas e não cabe no contexto do modelo de uma vez. As tools de transcrição devolvem 200 falas por chamada e dizem quantas faltam; o modelo passa `offset` para continuar. A API devolve a transcrição inteira — o janelamento é do servidor MCP. Registro sem transcrição não é erro: a tool responde `transcript unavailable (status: not_started)`, que é o contrato descrito em [Reuniões e ligações](/pt-br/guides/meetings-and-calls/). ## Corpo de e-mail custa mais que metadado `list_emails` e `get_email` devolvem metadado da conversa — participantes, caixas conectadas, contas vinculadas, contagens — e nunca o corpo. Ler as mensagens é outra tool, `list_email_messages`, e ela pede mais: `emails.content.read` além de `emails.read`, e uma política de rate limit própria, mais apertada que a do resto da API. Peça uma página por vez em vez de varrer a caixa inteira. Corpo com mais de 2.000 caracteres é cortado no texto que o modelo lê e vai inteiro no `structuredContent`. Conteúdo de anexo, corpo em HTML e `bcc` a API nunca serve, então nenhuma tool alcança. ## Texto de WhatsApp custa mais que metadado `list_whatsapp_conversations` e `get_whatsapp_conversation` devolvem metadado da conversa — contato, vendedor, contas vinculadas, instantes de atividade — e nunca uma mensagem. Ler as mensagens é outra tool, `list_whatsapp_messages`, e ela pede mais: `whatsapp.content.read` além de `whatsapp.read`, e uma política de rate limit própria. Peça uma página por vez em vez de varrer o histórico inteiro. Texto ou transcrição de áudio com mais de 2.000 caracteres é cortado no texto que o modelo lê e vai inteiro em `structuredContent`. Mensagem apagada para todos fica como lápide, sem conteúdo; mensagem editada mostra o texto atual. Conteúdo de mídia, identificadores do provedor, reações e estado de leitura nunca chegam ao modelo — a API não os serve. Veja a [referência de WhatsApp](/pt-br/reference/whatsapp/listwhatsappconversations/). ## O que o servidor resolve, e o que não Ele resolve renovação de token com margem de segurança, uma repetição em `401` com token novo, `Retry-After` em `429`, backoff em `503` e falha imediata nos outros `4xx`. Os erros chegam ao modelo com `code` e `request_id`, então um filtro de data mal formatado volta como `INVALID_DATETIME` e o modelo corrige o formato sozinho. Ele **não** pagina por você. O modelo segue `has_more` e `next_cursor` como qualquer outro cliente, e a descrição da tool avisa disso, porque [página curta não é a última página](/pt-br/guides/pagination/). Também não existe sync incremental, porque a v1 não tem: janele por `meeting_at` ou `created_at` e reprocesse de forma idempotente por `id`. ## Cota compartilhada Todas as credenciais de uma empresa dividem o mesmo bucket de 600 requisições por minuto. Um agente percorrendo um histórico longo consome a cota que as suas outras integrações estão usando; veja [Limites de uso](/pt-br/guides/rate-limits/). --- # Reuniões e ligações Source: https://docs.salesbud.com.br/pt-br/guides/meetings-and-calls Uma gravação capturada pelo bot de reunião e uma gravação capturada por integração de VoIP são coisas diferentes no produto, então são recursos diferentes na API. | | Reuniões | Ligações | | --- | --- | --- | | Coleção | `/v1/meetings` | `/v1/calls` | | `object` | `meeting` | `call` | | Prefixo do id | `mtg_` | `call_` | | De onde vem | Bot de reunião, upload na plataforma, app mobile | Integração de VoIP | As duas têm as mesmas cinco rotas — listar, obter, transcrição, respostas de template e avaliação geral — e carregam os mesmos campos, exceto `bot_history`, que só a reunião tem. O resto do que muda é a coleção e o prefixo do identificador. ## Não deduza o tipo pelo tipo de mídia `object` e `type` são eixos independentes: - `object` diz **o que o registro é** — `meeting` ou `call`. - `type` diz **como foi capturado** — `video` ou `audio`. Uma ligação pode ser em vídeo, e uma reunião pode ser em áudio. Numa empresa em produção, 3 das 49 ligações são vídeo e 6 das 863 reuniões são áudio. Decidir pelo `type` erra justamente esses casos. ```json { "id": "call_IGmhT13We_ltOPvjNSCHKw", "object": "call", "type": "audio" } { "id": "call_J781tlst89klU_mWPMXgDA", "object": "call", "type": "video" } ``` ## Identificadores não atravessam coleções Um id endereça um recurso em exatamente uma coleção. Pedir na outra é `404`, não redirecionamento: ```bash curl ".../v1/calls/mtg_H9_chV2YG6UGE0n31AqvDQ" -H "Authorization: Bearer $TOKEN" # 404 RESOURCE_NOT_FOUND ``` É de propósito: evita que um id resolva silenciosamente para um recurso de outro tipo, e faz a requisição errada aparecer no seu log em vez de devolver algo plausível. ## O que fica visível Um registro aparece na API só quando está **concluído** — quando terminou o processamento que produz transcrição, respostas e avaliações. Registros gravando, processando ou com falha não são retornados, em vez de serem expostos com `status` parcial. Por isso `status` é sempre `completed` na v1. A posse segue a **empresa padrão do dono** da reunião. Se a empresa padrão de um usuário muda, o histórico dele acompanha. É um trade-off conhecido de resolver posse em tempo de leitura; veja [Segurança](/pt-br/guides/security/). ## Escolhendo a coleção Use `/v1/meetings` para conversas de venda capturadas pelo bot ou enviadas por upload, e `/v1/calls` para conversas por telefone capturadas via VoIP. Se quiser as duas, pagine cada coleção separadamente — não existe endpoint combinado, porque na prática elas têm volumes e retenção diferentes. :::tip[Campos que se comportam diferente em ligações] A ligação não carrega `bot_history`: não há bot de gravação numa captura de VoIP, então o campo é omitido em vez de vir vazio — veja [o objeto ligação](/pt-br/objects/call/). `participants` numa ligação costuma ter um único item com `phone` em vez de `email`, porque o registro de VoIP identifica a outra ponta pelo número. `enablement.meeting_type` costuma vir `null` em ligações: o classificador foi feito para reunião de venda e não rotula ligação. ::: --- # Paginação Source: https://docs.salesbud.com.br/pt-br/guides/pagination As listagens são paginadas com cursores keyset assinados, ordenados por id crescente. A página padrão é 50 e o máximo é 100. ```json { "data": [ /* … */ ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_..." } } ``` ## Itere pelo `has_more`, nunca por `data.length` Este é o erro de integração mais comum contra esta API. Uma página pode voltar **curta, ou até vazia, com `has_more` em `true`**. O serviço limita quanto varre por requisição, então numa janela esparsa as linhas restantes vêm na chamada seguinte, em vez de numa resposta única e lenta. ```js // Certo let cursor = null; do { const page = await fetchPage(cursor); await handle(page.data); cursor = page.pagination.next_cursor; } while (cursor); ``` ```js // Errado — para cedo numa janela esparsa let page = await fetchPage(); while (page.data.length > 0) { /* … */ } ``` ## O cursor amarra seus filtros O `next_cursor` é assinado e cobre todos os filtros da requisição que o gerou. Devolva-o **sem alterar**, com os **mesmos filtros**. Mudar um filtro no meio da travessia, ou editar o cursor, devolve `400 INVALID_CURSOR` em vez de retornar silenciosamente uma fatia diferente. Para trocar de filtro, comece uma travessia nova, sem cursor. ## O que a ordenação por id garante Ordenar por id crescente torna a travessia estável sem snapshot: registros criados enquanto você pagina recebem ids maiores, então caem depois da sua posição atual e nunca deslocam uma página que você já leu. O trade-off é o espelho disso: um registro que fica **concluído** depois de você já ter passado pelo id dele não entra na travessia em andamento. Ele aparece na sua próxima passada por aquela janela. É por isso que a v1 não tem sync incremental — não existe carimbo dedicado de "ficou visível" que tornasse um delta honesto. O `updated_at` é informativo; não use como marca d'água de sincronização. Reconsulte por período: ```bash curl ".../v1/meetings?meeting_after=2026-01-01T00:00:00Z&meeting_before=2026-02-01T00:00:00Z" ``` ## Filtros | Parâmetro | Filtra por | | --- | --- | | `meeting_after` / `meeting_before` | Quando a reunião ou ligação aconteceu. É o filtro que a maioria das integrações quer. | | `created_after` / `created_before` | Quando o registro foi criado na Salesbud. | | `owner_email` | Dono exato, sem diferenciar maiúsculas. | | `type` | `video` ou `audio` — a mídia, não o tipo de recurso. | | `audience` | `internal` ou `external`. | | `has_transcript` | Se existe recurso de transcrição. | Todos os carimbos de tempo são RFC 3339 com offset explícito. `2026-01-01T00:00:00Z` é válido; `2026-01-01` não é, e devolve `400 INVALID_DATETIME`. --- # Limites de requisição Source: https://docs.salesbud.com.br/pt-br/guides/rate-limits As requisições são medidas por token buckets distribuídos. Vários se aplicam ao mesmo tempo, e o mais restrito vence. | Bucket | Aplica-se a | Cota | | --- | --- | --- | | `client` | Toda requisição do plano de dados, por credencial | 120/min, ajustável por credencial | | `company` | Toda requisição do plano de dados, somando as credenciais da empresa | 600/min | | `transcript` | Só leitura de transcrição, além dos dois acima | 60/min | | `oauth-client` | `POST /oauth/token`, por client id | 10/min | | `oauth-ip` | `POST /oauth/token`, por IP de origem | 30/min | Uma empresa pode ter até 20 credenciais ativas, e juntas elas nunca passam da cota da empresa. Use `GET /v1/context` para ler a cota de credencial em vigor para a sua. ## Headers Toda resposta traz o estado atual. O `RateLimit-Policy` lista todos os buckets que a requisição consumiu; o `RateLimit` informa apenas o que está mais perto de esgotar: ```http RateLimit-Policy: "client";q=120;w=60, "company";q=600;w=60 RateLimit: "client";r=118;t=1 ``` `q` é a cota, `w` a janela em segundos, `r` o que resta e `t` os segundos até o próximo token. O `RateLimit` nomeia o bucket com menos tokens no momento: normalmente o da sua credencial, o de transcrição nas leituras de transcrição, e o da empresa quando as outras credenciais estão gastando. Ritme suas chamadas pelos tetos do `RateLimit-Policy` — o bucket de uma leitura não é o único que te limita. O `r` conta tokens no bucket, cuja capacidade é o burst e não a cota, então ele não começa em `q` e decrementa. Leia como espaço disponível agora. Uma requisição recusada devolve `429 RATE_LIMIT_EXCEEDED` com `Retry-After` em segundos. Espere esse tempo — não repita na hora, e adicione jitter para que uma frota de workers não volte a sincronizar na janela seguinte. ## Comportamento quando o limitador degrada Os caminhos de leitura **falham abertos**: se o limitador ficar brevemente inacessível, as leituras de negócio continuam servindo em vez de cair. Emissão de token, leitura de transcrição e rotas internas de control plane **falham fechadas** com `503 SERVICE_UNAVAILABLE`. São os caminhos em que servir tráfego sem medição é pior que não servir. ## O limite do WAF é separado Há um limite grosseiro por IP no WAF, antes de o tráfego chegar ao serviço, e ele não faz parte das cotas acima: cerca de 2000 requisições por cinco minutos por IP de origem, e 100 por cinco minutos no `/oauth/token`. Ele falha de outro jeito, e isso importa quando você o encontra: - corta com `403`, não `429`, então parece falha de autorização; - não vem código de erro, nem `Retry-After`, nem headers de rate limit, porque a requisição nunca chega ao serviço; - conta por IP de origem, então toda integração atrás do mesmo endereço de saída divide o mesmo limite, com quais credenciais for. Um `403` sem nenhum corpo de erro nosso, especialmente no `/oauth/token`, é esse limite e não falta de escopo. ## Como ficar abaixo do limite - Pagine com `limit=100` em vez de muitas páginas pequenas — uma requisição devolve até 100 registros pelo mesmo custo de uma que devolve 10. - Filtre por período em vez de percorrer todo o histórico repetidamente. - Guarde o token de acesso; cada chamada a `/oauth/token` consome bucket próprio. - Busque transcrição só dos registros que você vai processar. O `has_transcript` permite filtrar a listagem antes. --- # Segurança Source: https://docs.salesbud.com.br/pt-br/guides/security ## Isolamento por empresa Toda leitura é filtrada pela empresa que está no token de acesso. A empresa vem da claim do token, nunca de um parâmetro da requisição, então não existe entrada capaz de ampliar o escopo de uma credencial. Os recursos são endereçados por identificadores opacos com prefixo (`mtg_`, `call_`, `usr_`, `tpl_`), alocados por empresa. Ids internos sequenciais nunca são expostos, então não dá para inferir volume nem enumerar ids. Um identificador que não é da sua empresa devolve `404`, nunca `403` — a API não confirma que o recurso existe em outro lugar. ## A posse é resolvida na leitura Um registro pertence à **empresa padrão do usuário dono dele**, avaliada no momento da consulta, e não congelada quando o registro foi criado. Vale dizer a consequência com todas as letras: **se a empresa padrão de um usuário muda, o histórico dele vai junto.** Um vendedor que troca de empresa dentro do mesmo grupo leva o histórico de reuniões para a API da empresa nova. É um trade-off deliberado de não desnormalizar a posse no registro. Se esse comportamento não couber no seu acordo de dados, levante isso com seu contato na Salesbud antes de construir em cima. ## Transcrições têm portão próprio `transcriptions.read` é um escopo distinto de `meetings.read` e `calls.read`, e uma credencial pode ter os escopos de coleção sem ele. A transcrição é o dado mais sensível que a API serve — a fala literal do cliente — então acessar o metadado de uma reunião não implica acessar o que foi dito nela. O portão soma em vez de substituir o escopo da coleção: a requisição de transcrição é checada contra os dois, então uma credencial restrita a ligações não alcança transcrição de reunião por ter `transcriptions.read`. O texto da transcrição é sanitizado antes de sair do serviço. ## Tudo é auditado Toda leitura de negócio é registrada: qual credencial, qual empresa, qual rota, quais ids de recurso e qual identidade chamou. A gravação da auditoria é **fail closed**. Se a trilha não puder ser escrita, a leitura não é devolvida — você recebe `503 AUDIT_UNAVAILABLE`. Uma resposta bem-sucedida, portanto, significa que o acesso ficou registrado. ## Higiene de credencial - Os secrets são guardados apenas como hash HMAC-SHA256 com pepper do serviço. Não podem ser lidos de volta, só rotacionados. - A rotação mantém o secret anterior válido por sete dias para você fazer deploy sem downtime; revogue assim que o novo estiver no ar. - Em suspeita de vazamento, **revogue o client** — isso invalida os tokens emitidos na hora. A rotação não, porque o secret antigo continua valendo. - Configure allowlist de IP quando a integração roda de endereços fixos. Ela vale na emissão e em toda requisição, então um token vazado não serve de fora. ## O que nunca sai do serviço Stack trace, caminho de arquivo, id interno, token, secret e corpo de requisição nunca são devolvidos numa resposta, nem escritos em log. --- # Issue an access token Source: https://docs.salesbud.com.br/pt-br/reference/authentication/issueaccesstoken post {"/oauth/token"} ### Autorização Sem autenticação. ### Exemplo de requisição ```bash curl -X POST https://api.salesbud.com.br/oauth/token \ -d 'grant_type=client_credentials' \ -d 'client_id=sb_client_...' \ -d 'client_secret=sb_secret_...' ``` ### Exemplo de resposta ```json title="200 OK" { "access_token": "eyJhbGciOiJSUzI1NiJ9.EXAMPLE_PAYLOAD.EXAMPLE_SIGNATURE", "token_type": "Bearer", "expires_in": 3600, "scope": "meetings.read calls.read transcriptions.read" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Access token issued. | | `400` | OAuth client-credentials request rejected. | | `401` | OAuth client authentication failed. | | `413` | The request body is larger than the accepted limit. | | `415` | The request media type is not supported by this endpoint. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get one completed call Source: https://docs.salesbud.com.br/pt-br/reference/calls/getcall get {"/v1/calls/{call_id}"} Devolve um [objeto ligação](/pt-br/objects/call/). ### Autorização Exige o escopo `calls.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `call_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": "call_ExampleCall01", "object": "call", "title": "Call to +55 11 99999-0000", "status": "completed", "type": "audio", "audience": "external", "meeting_at": "2026-01-15T18:28:01.000Z", "duration_seconds": 714, "no_show": false, "participants": [ { "phone": "+5511999990000", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Wants a demo before renewing the current contract." } ] }, "tags": [], "customer_questions": [], "competitors": [], "enablement": { "next_steps": "Online meeting booked for Thursday at 13:30; send the invite today.", "meeting_type": null, "speaking_duration": { "users": [], "others_seconds": 677 } }, "accounts": [], "overall_evaluation": { "status": "not_started", "justification": null }, "skill_scores": [], "transcript": { "available": false, "status": "not_started" }, "created_at": "2026-01-15T19:22:08.000Z", "updated_at": "2026-01-15T19:22:53.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped call. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List the default template answers for a call Source: https://docs.salesbud.com.br/pt-br/reference/calls/getcallanswers get {"/v1/calls/{call_id}/answers"} Devolve uma lista de [objetos resposta](/pt-br/objects/answer/). ### Autorização Exige o escopo `calls.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `call_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/answers" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "ans_ExampleAnswer01", "object": "meeting_answer", "question": { "id": "qst_ExampleQuestion01", "text": "What pains did the customer describe?", "type": "text", "order": 1 }, "answer": "Manual CRM entry after every call, about two hours a day.", "updated_at": "2026-01-14T14:10:43.000Z" } ], "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Answers generated for the call owner context. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get a call transcript Source: https://docs.salesbud.com.br/pt-br/reference/calls/getcalltranscript get {"/v1/calls/{call_id}/transcript"} Returns the enhanced transcript when available, otherwise the original transcript. An unavailable transcript is represented by a successful response with available set to false and its current processing status. Devolve um [objeto transcrição](/pt-br/objects/transcript/). ### Autorização Exige os escopos `calls.read` e `transcriptions.read` juntos. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `call_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/transcript" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "object": "transcript", "meeting_id": "call_ExampleCall01", "available": false, "status": "processing", "variant": null, "utterances": [], "created_at": null, "updated_at": null }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped call transcript. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get the overall call evaluation Source: https://docs.salesbud.com.br/pt-br/reference/calls/getoverallcallevaluation get {"/v1/calls/{call_id}/evaluations/overall"} Devolve um [objeto avaliação](/pt-br/objects/evaluation/). ### Autorização Exige o escopo `calls.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `call_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/calls/call_01JEXAMPLE/evaluations/overall" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": null, "object": "overall_meeting_evaluation", "meeting_id": "call_ExampleCall01", "status": "not_started", "score": null, "justification": null, "created_at": null, "updated_at": null }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Overall evaluation state and result. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List completed calls Source: https://docs.salesbud.com.br/pt-br/reference/calls/listcalls get {"/v1/calls"} Calls are the records captured from a VoIP integration, addressed by their own `call_` identifiers. They answer the same five routes as meetings and carry the same fields except `bot_history`: a VoIP capture has no recording bot, so the field is absent, not empty. Devolve uma lista de [objetos ligação](/pt-br/objects/call/). ### Autorização Exige o escopo `calls.read`. ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | | `created_after` | `string date-time` | Não | Return records created after this instant. | | `created_before` | `string date-time` | Não | Return records created before this instant. | | `meeting_after` | `string date-time` | Não | Return records whose event instant is after this value. | | `meeting_before` | `string date-time` | Não | Return records whose event instant is before this value. | | `owner_email` | `string email` | Não | Exact case-insensitive owner email match. | | `type` | `video` · `audio` | Não | Media the record was captured as. Independent of the resource kind: a call can be video and a meeting can be audio. | | `audience` | `internal` · `external` | Não | Whether the record had participants from outside the company (external) or only internal ones. | | `has_transcript` | `boolean` | Não | Filter by existence of a transcript resource. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/calls?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "call_ExampleCall01", "object": "call", "title": "Call to +55 11 99999-0000", "status": "completed", "type": "audio", "audience": "external", "meeting_at": "2026-01-15T18:28:01.000Z", "duration_seconds": 714, "no_show": false, "participants": [ { "phone": "+5511999990000", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Wants a demo before renewing the current contract." } ] }, "tags": [], "customer_questions": [], "competitors": [], "enablement": { "next_steps": "Online meeting booked for Thursday at 13:30; send the invite today.", "meeting_type": null, "speaking_duration": { "users": [], "others_seconds": 677 } }, "accounts": [], "overall_evaluation": { "status": "not_started", "justification": null }, "skill_scores": [], "transcript": { "available": false, "status": "not_started" }, "created_at": "2026-01-15T19:22:08.000Z", "updated_at": "2026-01-15T19:22:53.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor02" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped call page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get the authenticated integration context Source: https://docs.salesbud.com.br/pt-br/reference/context/getapicontext get {"/v1/context"} ### Autorização Sem autenticação. ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/context" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "object": "api_context", "client": { "id": "sb_client_ExampleClient01", "name": "acme-sync" }, "company": { "id": "cmp_ExampleCompany01", "name": "Example Company" }, "scopes": [ "meetings.read", "calls.read", "transcriptions.read" ], "rate_limit": { "requests_per_minute": 120 } }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | API client, company, scopes and rate-limit context. | | `401` | Missing or invalid credentials. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get an email conversation Source: https://docs.salesbud.com.br/pt-br/reference/emails/getemail get {"/v1/emails/{email_id}"} Conversation metadata only. An `eml_` id issued to another company, or any id with a different prefix, is a 404, not a hint. ### Autorização Exige o escopo `emails.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `email_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/emails/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": "eml_01JEXAMPLE", "object": "email_conversation", "subject": "Assunto de exemplo", "first_message_at": "2026-01-10T12:00:00.000Z", "last_message_at": "2026-01-12T15:30:00.000Z", "message_count": 3, "has_attachments": true, "last_message_direction": "inbound", "participants": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true }, { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false } ], "mailboxes": [ { "address": "vendedora@example.com", "owner": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" } }, { "address": "vendas@example.com", "owner": null } ], "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ] }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped email conversation. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List the messages of an email conversation Source: https://docs.salesbud.com.br/pt-br/reference/emails/listemailmessages get {"/v1/emails/{email_id}/messages"} Messages in the order they were sent, oldest first, with plain-text bodies and attachment metadata (no attachment content). Governed by the `email_content` rate-limit policy in addition to the client and company policies. The cursor is bound to the conversation. ### Autorização Exige os escopos `emails.read` e `emails.content.read` juntos. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `email_id` | `string` | Sim | — | ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/emails/mtg_01JEXAMPLE/messages?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "emsg_01JEXAMPLE", "object": "email_message", "conversation_id": "eml_01JEXAMPLE", "sent_at": "2026-01-12T15:30:00.000Z", "direction": "inbound", "subject": "Re: Assunto de exemplo", "snippet": "Trecho de exemplo da mensagem", "body_text": "Corpo de exemplo da mensagem.\n\nAtenciosamente,\nCliente Exemplo", "from": { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false }, "to": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true } ], "cc": [ { "address": "financeiro@example.net", "name": null, "internal": false } ], "attachments": [ { "filename": "exemplo.pdf", "mime_type": "application/pdf", "size_bytes": 182400 } ] } ], "pagination": { "limit": 50, "has_more": false, "next_cursor": null }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped email message page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List email conversations Source: https://docs.salesbud.com.br/pt-br/reference/emails/listemails get {"/v1/emails"} Conversations are deduplicated across every mailbox connected to the company: two sellers on the same thread yield one conversation with two mailboxes. Ordered by `last_message_at` ascending (oldest activity first); pages forward with a signed keyset cursor. Message bodies live under `/v1/emails/{email_id}/messages` and need `emails.content.read`. ### Autorização Exige o escopo `emails.read`. ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | | `last_message_after` | `string date-time` | Não | Return conversations whose latest message is after this instant. Filters on activity, not on record changes: a conversation that gains a message moves forward and will be seen again. | | `last_message_before` | `string date-time` | Não | Return conversations whose latest message is before this instant. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/emails?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "eml_01JEXAMPLE", "object": "email_conversation", "subject": "Assunto de exemplo", "first_message_at": "2026-01-10T12:00:00.000Z", "last_message_at": "2026-01-12T15:30:00.000Z", "message_count": 3, "has_attachments": true, "last_message_direction": "inbound", "participants": [ { "address": "vendedora@example.com", "name": "Vendedora Exemplo", "internal": true }, { "address": "cliente@example.net", "name": "Cliente Exemplo", "internal": false } ], "mailboxes": [ { "address": "vendedora@example.com", "owner": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" } }, { "address": "vendas@example.com", "owner": null } ], "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ] } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor03" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped email conversation page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get one completed meeting Source: https://docs.salesbud.com.br/pt-br/reference/meetings/getmeeting get {"/v1/meetings/{meeting_id}"} Devolve um [objeto reunião](/pt-br/objects/meeting/). ### Autorização Exige o escopo `meetings.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `meeting_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": "mtg_ExampleMeeting01", "object": "meeting", "title": "Discovery - Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "no_show": false, "participants": [ { "display_name": "Example Buyer", "email": "buyer@example.com", "kind": "external" }, { "email": "procurement@example.com", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Manual CRM entry after every call, about two hours a day." } ] }, "tags": [ { "id": "tag_ExampleTag01", "name": "Enterprise" } ], "customer_questions": [ { "question": "Does it integrate with our CRM?", "category": "integrations" } ], "competitors": [ { "id": "ctr_ExampleCompetitor01", "name": "Example CRM" } ], "enablement": { "next_steps": "Send the proposal by Friday covering 5,400 seats.", "meeting_type": "qualification", "speaking_duration": { "users": [ { "user": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com" }, "duration_seconds": 1760 } ], "others_seconds": 1080 } }, "accounts": [ { "id": "acc_ExampleAccount01", "domain": "acme.example.com", "cnpj": "12345678000199" } ], "overall_evaluation": { "status": "completed", "justification": "Strong discovery, weak on next steps." }, "skill_scores": [], "bot_history": [], "transcript": { "available": true, "status": "completed" }, "created_at": "2026-01-14T12:58:47.000Z", "updated_at": "2026-01-14T14:10:43.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped meeting. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List the default template answers for a meeting Source: https://docs.salesbud.com.br/pt-br/reference/meetings/getmeetinganswers get {"/v1/meetings/{meeting_id}/answers"} Devolve uma lista de [objetos resposta](/pt-br/objects/answer/). ### Autorização Exige o escopo `meetings.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `meeting_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/answers" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "ans_ExampleAnswer01", "object": "meeting_answer", "question": { "id": "qst_ExampleQuestion01", "text": "What pains did the customer describe?", "type": "text", "order": 1 }, "answer": "Manual CRM entry after every call, about two hours a day.", "updated_at": "2026-01-14T14:10:43.000Z" } ], "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Answers generated for the meeting owner context. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get a meeting transcript Source: https://docs.salesbud.com.br/pt-br/reference/meetings/getmeetingtranscript get {"/v1/meetings/{meeting_id}/transcript"} Returns the enhanced transcript when available, otherwise the original transcript. An unavailable transcript is represented by a successful response with available set to false and its current processing status. Devolve um [objeto transcrição](/pt-br/objects/transcript/). ### Autorização Exige os escopos `meetings.read` e `transcriptions.read` juntos. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `meeting_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/transcript" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "object": "transcript", "meeting_id": "mtg_ExampleMeeting01", "available": true, "status": "completed", "variant": "enhanced", "utterances": [ { "speaker": "Example Rep", "text": "Walk me through how the team logs a call today.", "start_ms": 12400, "end_ms": 16800 }, { "speaker": "buyer@example.com", "text": "Manually, right after. It costs us about two hours a day.", "start_ms": 17200, "end_ms": 23900 } ], "created_at": "2026-01-14T14:02:00.000Z", "updated_at": "2026-01-14T14:09:12.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped meeting transcript. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get the overall meeting evaluation Source: https://docs.salesbud.com.br/pt-br/reference/meetings/getoverallmeetingevaluation get {"/v1/meetings/{meeting_id}/evaluations/overall"} Devolve um [objeto avaliação](/pt-br/objects/evaluation/). ### Autorização Exige o escopo `meetings.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `meeting_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/meetings/mtg_01JEXAMPLE/evaluations/overall" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": "evl_ExampleEvaluation01", "object": "overall_meeting_evaluation", "meeting_id": "mtg_ExampleMeeting01", "status": "completed", "score": 8, "justification": "Strong discovery, weak on next steps.", "created_at": "2026-01-14T14:05:00.000Z", "updated_at": "2026-01-14T14:10:43.000Z" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Overall evaluation state and result. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List completed meetings Source: https://docs.salesbud.com.br/pt-br/reference/meetings/listmeetings get {"/v1/meetings"} Devolve uma lista de [objetos reunião](/pt-br/objects/meeting/). ### Autorização Exige o escopo `meetings.read`. ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | | `created_after` | `string date-time` | Não | Return records created after this instant. | | `created_before` | `string date-time` | Não | Return records created before this instant. | | `meeting_after` | `string date-time` | Não | Return records whose event instant is after this value. | | `meeting_before` | `string date-time` | Não | Return records whose event instant is before this value. | | `owner_email` | `string email` | Não | Exact case-insensitive owner email match. | | `type` | `video` · `audio` | Não | Media the record was captured as. Independent of the resource kind: a call can be video and a meeting can be audio. | | `audience` | `internal` · `external` | Não | Whether the record had participants from outside the company (external) or only internal ones. | | `has_transcript` | `boolean` | Não | Filter by existence of a transcript resource. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/meetings?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "mtg_ExampleMeeting01", "object": "meeting", "title": "Discovery - Acme", "status": "completed", "type": "video", "audience": "external", "meeting_at": "2026-01-14T13:00:00.000Z", "duration_seconds": 2840, "no_show": false, "participants": [ { "display_name": "Example Buyer", "email": "buyer@example.com", "kind": "external" }, { "email": "procurement@example.com", "kind": "external" } ], "owner": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com", "team": { "id": "team_ExampleTeam01", "name": "Inside Sales", "role": "participant", "is_default": true } }, "template": { "id": "tpl_ExampleTemplate01", "name": "Discovery script", "answers": [ { "field": "qst_ExampleQuestion01", "question": "What pains did the customer describe?", "answer": "Manual CRM entry after every call, about two hours a day." } ] }, "tags": [ { "id": "tag_ExampleTag01", "name": "Enterprise" } ], "customer_questions": [ { "question": "Does it integrate with our CRM?", "category": "integrations" } ], "competitors": [ { "id": "ctr_ExampleCompetitor01", "name": "Example CRM" } ], "enablement": { "next_steps": "Send the proposal by Friday covering 5,400 seats.", "meeting_type": "qualification", "speaking_duration": { "users": [ { "user": { "id": "usr_ExampleOwner01", "name": "Example Rep", "email": "rep@example.com" }, "duration_seconds": 1760 } ], "others_seconds": 1080 } }, "accounts": [ { "id": "acc_ExampleAccount01", "domain": "acme.example.com", "cnpj": "12345678000199" } ], "overall_evaluation": { "status": "completed", "justification": "Strong discovery, weak on next steps." }, "skill_scores": [], "bot_history": [], "transcript": { "available": true, "status": "completed" }, "created_at": "2026-01-14T12:58:47.000Z", "updated_at": "2026-01-14T14:10:43.000Z" } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor01" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped meeting page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Get a WhatsApp conversation Source: https://docs.salesbud.com.br/pt-br/reference/whatsapp/getwhatsappconversation get {"/v1/whatsapp/{whatsapp_id}"} Conversation metadata only: contact, seller, linked accounts and activity instants. A `wa_` id issued to another company, or any id with a different prefix, is a 404, not a hint. A deleted chat keeps answering, with `deleted_at` and `deleted_reason` set. ### Autorização Exige o escopo `whatsapp.read`. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `whatsapp_id` | `string` | Sim | — | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/whatsapp/mtg_01JEXAMPLE" \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": { "id": "wa_01JEXAMPLE", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": false, "contact": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ], "first_message_at": "2026-09-10T12:00:00.000Z", "last_message_at": "2026-09-12T15:30:00.000Z", "last_message_direction": "inbound", "deleted_at": null, "deleted_reason": null }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped WhatsApp conversation. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List WhatsApp conversations Source: https://docs.salesbud.com.br/pt-br/reference/whatsapp/listwhatsappconversations get {"/v1/whatsapp"} One conversation per chat of a company seller, from WhatsApp Web or RD Conversas. Ordered by `last_message_at` ascending (oldest activity first); pages forward with a signed keyset cursor. `last_message_at` moves only when a message arrives: an edit or a deletion does not move the conversation, so re-read messages to observe them. Message content lives under `/v1/whatsapp/{whatsapp_id}/messages` and needs `whatsapp.content.read`. ### Autorização Exige o escopo `whatsapp.read`. ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | | `last_message_after` | `string date-time` | Não | Return conversations whose latest message is after this instant. Filters on activity, not on record changes: a conversation that gains a message moves forward and will be seen again. | | `last_message_before` | `string date-time` | Não | Return conversations whose latest message is before this instant. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/whatsapp?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "wa_01JEXAMPLE", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": false, "contact": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [ { "id": "acc_01JEXAMPLE", "domain": "example.net", "cnpj": "00.000.000/0001-00" } ], "first_message_at": "2026-09-10T12:00:00.000Z", "last_message_at": "2026-09-12T15:30:00.000Z", "last_message_direction": "inbound", "deleted_at": null, "deleted_reason": null }, { "id": "wa_01JGROUP", "object": "whatsapp_conversation", "source": "whatsapp_web", "is_group": true, "contact": { "phone": null, "name": "Grupo Vendas Exemplo" }, "seller": { "user": { "id": "usr_01JEXAMPLE", "name": "Vendedora Exemplo", "email": "vendedora@example.com" }, "phone": "+5511988880001" }, "accounts": [], "first_message_at": "2026-09-11T09:00:00.000Z", "last_message_at": "2026-09-12T18:00:00.000Z", "last_message_direction": "outbound", "deleted_at": null, "deleted_reason": null } ], "pagination": { "limit": 50, "has_more": true, "next_cursor": "cur_ExampleCursor04" }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped WhatsApp conversation page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # List the messages of a WhatsApp conversation Source: https://docs.salesbud.com.br/pt-br/reference/whatsapp/listwhatsappmessages get {"/v1/whatsapp/{whatsapp_id}/messages"} Messages in the order they were sent, oldest first, with sanitized text, media metadata (no media content) and the audio transcription when one exists. A message deleted for everyone stays in place as a tombstone with `deleted_at` set and every content field null; an edited message carries its current text and `edited_at`. Governed by the `whatsapp_content` rate-limit policy in addition to the client and company policies. The cursor is a keyset over `(sent_at, id)` bound to the conversation. ### Autorização Exige os escopos `whatsapp.read` e `whatsapp.content.read` juntos. ### Parâmetros de caminho | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `whatsapp_id` | `string` | Sim | — | ### Parâmetros de query | Nome | Tipo | Obrigatório | Descrição | | --- | --- | --- | --- | | `limit` | `integer` | Não | Records per page, 1 to 100. A page may return fewer than this while has_more is still true; drive the loop by has_more, not by the size of data. | | `cursor` | `string` | Não | Signed cursor returned by the previous page. | ### Exemplo de requisição ```bash curl "https://api.salesbud.com.br/v1/whatsapp/mtg_01JEXAMPLE/messages?limit=...&cursor=..." \ -H "Authorization: Bearer $SALESBUD_ACCESS_TOKEN" ``` ### Exemplo de resposta ```json title="200 OK" { "data": [ { "id": "wamsg_01JEXAMPLE1", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:30:00.000Z", "direction": "inbound", "author": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "type": "text", "text": "Pode me mandar a proposta?", "media": null, "transcription": null, "edited_at": null, "deleted_at": null, "deleted_reason": null }, { "id": "wamsg_01JEXAMPLE2", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:32:00.000Z", "direction": "outbound", "author": { "phone": "+5511988880001", "name": "Vendedora Exemplo" }, "type": "audio", "text": null, "media": { "file_name": null, "mime_type": "audio/ogg; codecs=opus", "size_bytes": 48213 }, "transcription": "Claro, envio ainda hoje por e-mail.", "edited_at": null, "deleted_at": null, "deleted_reason": null }, { "id": "wamsg_01JEXAMPLE3", "object": "whatsapp_message", "conversation_id": "wa_01JEXAMPLE", "sent_at": "2026-09-12T15:35:00.000Z", "direction": "inbound", "author": { "phone": "+5511999990001", "name": "Cliente Exemplo" }, "type": "text", "text": null, "media": null, "transcription": null, "edited_at": null, "deleted_at": "2026-09-12T15:36:00.000Z", "deleted_reason": "revoked_for_everyone" } ], "pagination": { "limit": 50, "has_more": false, "next_cursor": null }, "request_id": "req_ExampleRequestId01" } ``` ### Respostas | Status | Descrição | | --- | --- | | `200` | Company-scoped WhatsApp message page. | | `400` | Invalid request. | | `401` | Missing or invalid credentials. | | `403` | The token does not contain the required scope. | | `404` | Resource not found in the authenticated company. | | `429` | Client rate limit exceeded. | | `503` | A required audit or rate-limit dependency is unavailable. | :::note Esta página é gerada a partir da especificação OpenAPI. Não edite à mão. ::: --- # Changelog Source: https://docs.salesbud.com.br/pt-br/resources/changelog Mudanças que quebram uma versão já publicada são anunciadas antes de entrar. Mudanças aditivas — campo novo, endpoint novo, código de erro novo — podem chegar a qualquer momento, então faça parse defensivo: ignore campos que você não reconhece. ## Não publicado — primeira entrega A versão `v1` ainda não foi liberada para parceiros. O que segue é a forma da primeira entrega. **Ferramentas** - Um servidor MCP oficial acompanha a v1: `@salesbud/mcp` no npm, uma tool por operação pública de leitura, para Claude Desktop, Claude Code e Cursor. Ele é cliente desta API e não acrescenta capacidade nenhuma. Veja [Servidor MCP](/pt-br/guides/mcp/). **Recursos** - Ligações são coleção própria em `/v1/calls`, com identificadores `call_`. Um registro capturado por integração de VoIP tem `object: "call"`; o resto é `meeting`. Ids não resolvem entre coleções. - As cinco rotas são espelhadas nas duas coleções: listar, obter, transcrição, respostas de template e avaliação geral. - A ligação não carrega `bot_history`: captura de VoIP não tem bot de gravação, então o campo é omitido em vez de voltar vazio, e um schema que valida ligação o recusa. `enablement.meeting_type` fica e costuma vir `null` — esse nulo é legítimo, diferente do bot. - Conversas de WhatsApp dos vendedores da empresa são uma coleção em `/v1/whatsapp`, com identificadores `wa_` e mensagens `wamsg_`, atrás de `whatsapp.read` (metadado) e `whatsapp.content.read` (texto sanitizado, metadado de mídia, transcrição de áudio). `last_message_at` só anda com mensagem nova, então releia as mensagens para ver edições e apagamentos; mensagem apagada fica como lápide. Filtros de data comparam em segundos inteiros. Veja [WhatsApp](/pt-br/reference/whatsapp/listwhatsappconversations/). **Erros** - O envelope de erro é `{ type, title, detail, code, request_id }`. Todo campo é snake_case; o status HTTP fica na status line e não se repete no corpo. Ramifique pelo `code`. - `POST /oauth/token` mantém o formato de erro do OAuth 2.0 exigido pela RFC 6749 §5.2. **Paginação e filtros** - Os cursores são assinados e ordenados por id crescente; eles amarram os filtros da requisição que os gerou. - `updated_after`, `updated_before` e `snapshot_at` foram removidos. Sem carimbo dedicado de visibilidade não há sync incremental honesto; reconsulte por período. O `updated_at` continua nas respostas, como informativo. - Os filtros de data são `meeting_after` e `meeting_before`. Versões anteriores também aceitavam `meeting_at_from` e `meeting_at_to`; eles nunca chegaram a parceiro e foram removidos, em vez de nascerem descontinuados. **Acesso** - A API é gateada pela feature `API_ACCESS` da empresa, verificada na emissão de token e na criação de credencial. - Emails e domínios de conta são normalizados para minúsculo em todos os campos, então um endereço pode ser usado como chave de junção independentemente de onde apareça. --- # Para LLMs e agentes Source: https://docs.salesbud.com.br/pt-br/resources/for-agents Esta documentação é publicada também em formato legível por máquina, para que um agente a consuma sem raspar markup. ## Pontos de entrada | Arquivo | Conteúdo | | --- | --- | | [`/llms.txt`](/llms.txt) | Índice: uma linha por página, com URL e resumo. Comece por aqui. | | [`/llms-full.txt`](/llms-full.txt) | Todas as páginas concatenadas em Markdown puro, na ordem de leitura. | | [`/openapi.yaml`](/openapi.yaml) | O contrato normativo. Tudo na referência da API é gerado dele. | O formato segue a [convenção llms.txt](https://llmstxt.org/). ## Qualquer página em Markdown Acrescente `.md` a qualquer URL da documentação para obter a fonte em vez da página renderizada: ``` https://docs.salesbud.com.br/pt-br/guides/pagination/ → HTML https://docs.salesbud.com.br/pt-br/guides/pagination.md → Markdown ``` As duas versões de idioma estão disponíveis; as páginas em português ficam sob `/pt-br/`. ## Se você só precisa ler dado a partir de um agente Esta API serve um servidor MCP em `https://api.salesbud.com.br/mcp`, que o claude.ai alcança como conector personalizado. Ele expõe todas as operações públicas de leitura como tools e cuida de renovação de token, retries e janelamento de transcrição. É o caminho mais curto entre uma credencial e um agente lendo reuniões — veja [Servidor MCP](/pt-br/guides/mcp/). Escreva um cliente HTTP quando estiver construindo um serviço, e não pilotando um modelo. ## Se você vai gerar código de integração Três coisas erram com facilidade e vale ler antes de escrever qualquer linha: 1. **Itere por `pagination.has_more`, nunca por `data.length`.** Uma página pode vir curta ou vazia com dado restante. Veja [Paginação](/pt-br/guides/pagination/). 2. **Ramifique por `error.code`, nunca por `error.detail`.** O detalhe é texto humano e pode ser reescrito. Veja [Erros](/pt-br/guides/errors/). 3. **Não deduza o tipo de recurso pelo `type`.** O `object` diz se é `meeting` ou `call`; o `type` diz só `video` ou `audio`, e os dois são independentes. Veja [Reuniões e ligações](/pt-br/guides/meetings-and-calls/). Não existe refresh token — renovar é chamar `/oauth/token` de novo. Isso é propriedade do fluxo client credentials, não omissão.