A reference guide for building AI agents: every method, how to authenticate, and the permissions each one needs.
The Spendesk API is how an app or AI agent works with a company's spend data: reading settlements and payables, pulling the receipts attached to an expense, and listing suppliers, members, and cost centers. Access is granted through credentials that are exchanged for a bearer token, and that token carries only the scopes chosen when the credentials were created, so an agent reaches just the kinds of spend data it was granted. The API is read-first, and Spendesk also runs a hosted server that exposes read access to agents and can push events when a payable or settlement is created.
How an app or AI agent connects to Spendesk determines what it can reach. There is a route for making calls, a route for receiving events, and a hosted server that exposes Spendesk to agents, and each is governed by the credentials behind it and the scopes those credentials carry.
The REST API answers at https://public-api.spendesk.com under the v1 path, with a separate sandbox at https://public-api.demo.spendesk.com. It is read-first: most resources are read-only, and a few areas, like suppliers, cost centers, and purchase orders, also allow writes.
Spendesk hosts its own Model Context Protocol server at https://public-api.spendesk.com/v1/mcp, marketed as Spendesk AI Connect. It is read-only, lets an agent query spend data, payables, suppliers, purchase orders, cost centers, and settlements in plain language, and is in beta with general availability planned. It authorizes through the same scoped credentials as the REST API.
Webhooks deliver the chosen events to a receiver URL the company registers, signed with a secret so the receiver can confirm the payload came from Spendesk. The events cover payables being created or prepared and settlements being created. This area is marked experimental.
A company's Account Owner creates a client ID and secret in Settings, picks the scopes it carries, and sets an expiry of up to one year. The credentials are exchanged for a bearer access token at the token endpoint, sending the ID and secret base64-encoded with the Basic prefix. Available on Premium and Enterprise plans.
A partner building an integration runs an authorization-code flow with PKCE: a connected company signs in and grants the chosen scopes, and the resulting code is exchanged for a bearer access token. The token acts on behalf of that company, limited to the scopes it granted.
The Spendesk API is split into areas an agent can act on, like settlements, payables, suppliers, members, and cost centers. Most areas are read-only, and the scope chosen when credentials are created decides which areas a given agent can reach at all.
Read settlements, payables and their receipts, request asynchronous payable snapshots, and read bank fees, wallet loads, and the wallet summary.
List and read suppliers, list and read the members (users) of a company, create and update suppliers, and archive them.
Read analytical fields and their values, read expense categories, and read, create, update, or delete cost centers.
List and read purchase orders, create a purchase order, and cancel or close one.
Read journal templates, create an accounting export, read a journal's content, and check the export status of entities.
Exchange Public API credentials for an access token, or run the partner authorization flow to obtain a token on behalf of a connected company.
Create, list, read, update, and delete webhook instances that subscribe to payable and settlement events.
The hosted Model Context Protocol endpoint that exposes read access to spend data, suppliers, members, and cost centers to an AI agent.
Filter by method, access, or permission, or search any path. Select a row for version detail, rate limits, the related webhook event, and the source.
| Method | Endpoint | What it does | Access | Permission | Version | |
|---|---|---|---|---|---|---|
Spend dataRead settlements, payables and their receipts, request asynchronous payable snapshots, and read bank fees, wallet loads, and the wallet summary.8 | ||||||
| GET | /v1/settlements | List settlements, the records of money moving out of the company's Spendesk wallet, with filters for date, payment account, state, and supplier. | read | settlement:read | Current | |
The bearer token must carry the settlement:read scope, chosen when the credentials were created. Settlement data is served from a cache, so a response can be up to 86,400 seconds old. Acts onsettlement Permission (capability) settlement:readVersionAvailable since the API’s base version Webhook event settlements-createdRate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/payables/{id} | Read a single payable by its ID. A payable is one expense that creates a liability, whether a card purchase, an invoice, or an expense claim. | read | payable:read | Current | |
Requires the payable:read scope. Payable data is served from a cache, so a response can be up to 1,200 seconds old. Acts onpayable Permission (capability) payable:readVersionAvailable since the API’s base version Webhook event payables-createdRate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/snapshots/payables | Request an extract of many payables. The call queues the work and returns a key immediately, rather than returning the payables in the response. | read | payable:read | Current | |
Requires the payable:read scope. This is the supported way to list many payables, since the synchronous list was removed at general availability. Snapshot requests must be made one at a time, not in parallel. Acts onpayable snapshot Permission (capability) payable:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/snapshots/payables/{key} | Read a payable snapshot by its key. Once the snapshot's status is COMPLETE, the payables are returned under the result element of the response. | read | payable:read | Current | |
Requires the payable:read scope. The snapshot is built asynchronously, so this is polled until its status is COMPLETE. Acts onpayable snapshot Permission (capability) payable:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/payables/{payableId}/attachments | List the receipts and invoices attached to a payable, returned as short-lived download URLs to be saved to the caller's own storage. | read | payable-attachment:read | Current | |
Requires the payable-attachment:read scope, which is separate from payable:read because attachments hold receipt images. The download URLs expire, and the response is cached for up to 3,300 seconds. Acts onpayable attachment Permission (capability) payable-attachment:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/bank-fees | List the bank fees charged on the company's Spendesk wallet. | read | bank-fee:read | Current | |
Requires the bank-fee:read scope. Bank fee data is served from a cache, so a response can be up to 86,400 seconds old. Acts onbank fee Permission (capability) bank-fee:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/wallet-loads | List the wallet loads of the company, the top-ups added to the Spendesk wallet by bank transfer or by card. | read | wallet-load:read | Current | |
Requires the wallet-load:read scope. Wallet load data is served from a cache, so a response can be up to 86,400 seconds old. Acts onwallet load Permission (capability) wallet-load:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/wallet-summary | Read the wallet summary for the company, such as its name and remaining balance. | read | wallet-summary:read | Current | |
Requires the wallet-summary:read scope. The summary is served from a cache, so a response can be up to 86,400 seconds old. Acts onwallet summary Permission (capability) wallet-summary:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Suppliers & membersList and read suppliers, list and read the members (users) of a company, create and update suppliers, and archive them.6 | ||||||
| GET | /v1/suppliers | List the suppliers (vendors) that exist in the company, along with their bank account details where present. | read | supplier:read | Current | |
Requires the supplier:read scope. Supplier data is served from a cache, so a response can be up to 604,800 seconds old. Acts onsupplier Permission (capability) supplier:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/suppliers/{id} | Read a single supplier by its ID. | read | supplier:read | Current | |
Requires the supplier:read scope. Acts onsupplier Permission (capability) supplier:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/suppliers | Create one or more suppliers in the company. | write | supplier:manage | Current | |
Requires the experimental:supplier:manage scope. This write area is marked experimental and can change before it settles. Acts onsupplier Permission (capability) supplier:manageVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| PATCH | /v1/suppliers/{id} | Update a supplier's details. | write | supplier:manage | Current | |
Requires the experimental:supplier:manage scope. This write area is marked experimental and can change before it settles. Acts onsupplier Permission (capability) supplier:manageVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/users | List the members (users) of the company, the people who can spend or approve in Spendesk. | read | user:read | Current | |
Requires the user:read scope. This returns people's names and email addresses. User data is served from a cache, so a response can be up to 1,200 seconds old. Acts onmember Permission (capability) user:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/users/{id} | Read a single member (user) by their ID. | read | user:read | Current | |
Requires the user:read scope, which returns a person's name and email address. Acts onmember Permission (capability) user:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Analytical & cost centersRead analytical fields and their values, read expense categories, and read, create, update, or delete cost centers.7 | ||||||
| GET | /v1/analytical-fields | List the analytical fields, the custom dimensions a company uses to label spend, including archived ones. | read | analytical-field:read | Current | |
Requires the analytical-field:read scope. The data is served from a cache, so a response can be up to 604,800 seconds old. Acts onanalytical field Permission (capability) analytical-field:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/analytical-fields/{fieldId}/values | List the values that belong to a single analytical field. | read | analytical-field:read | Current | |
Requires the analytical-field:read scope. Acts onanalytical value Permission (capability) analytical-field:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/cost-centers | List the cost centers of the company. | read | cost-center:read | Current | |
Requires the cost-center:read scope. Cost center data is served from a cache, so a response can be up to 86,400 seconds old. Acts oncost center Permission (capability) cost-center:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/cost-centers | Create one or more cost centers in the company. | write | cost-center:manage | Current | |
Requires the cost-center:manage scope, which is separate from the read scope. Acts oncost center Permission (capability) cost-center:manageVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| PUT | /v1/cost-centers/{costCenterId} | Update a cost center. | write | cost-center:manage | Current | |
Requires the cost-center:manage scope. Acts oncost center Permission (capability) cost-center:manageVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| DELETE | /v1/cost-centers/{costCenterId} | Delete a cost center. | write | cost-center:manage | Current | |
Requires the cost-center:manage scope. Acts oncost center Permission (capability) cost-center:manageVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/expense-categories | List the expense categories of the company. | read | expense-category:read | Current | |
Requires the expense-category:read scope. The data is served from a cache, so a response can be up to 86,400 seconds old. Acts onexpense category Permission (capability) expense-category:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Purchase ordersList and read purchase orders, create a purchase order, and cancel or close one.3 | ||||||
| GET | /v1/purchase-orders | List purchase orders raised in the company. | read | purchase-order:read | Current | |
Requires the experimental:purchase-order:read scope. This area is marked experimental and can change before it settles. Acts onpurchase order Permission (capability) purchase-order:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/purchase-orders | Create a purchase order. | write | purchase-order:write | Current | |
Requires the experimental:purchase-order:write scope. This area is marked experimental and can change before it settles. Acts onpurchase order Permission (capability) purchase-order:writeVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/purchase-orders/{purchaseOrderId}/cancel | Cancel a purchase order. | write | purchase-order:write | Current | |
Requires the experimental:purchase-order:write scope. This area is marked experimental and can change before it settles. Acts onpurchase order Permission (capability) purchase-order:writeVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Accounting exportRead journal templates, create an accounting export, read a journal's content, and check the export status of entities.2 | ||||||
| POST | /v1/experimental/journals | Create an accounting export, producing the journal entries for spend over a chosen period. | write | accounting-export:read | Current | |
Requires the experimental:accounting-export:read scope, the single scope that covers this whole experimental area. This area is marked experimental and can change before it settles. Acts onaccounting export Permission (capability) accounting-export:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/experimental/journals/{key} | Read a journal's content as CSV, using the key returned when the export was created. | read | accounting-export:read | Current | |
Requires the experimental:accounting-export:read scope. This area is marked experimental and can change before it settles. Acts onjournal Permission (capability) accounting-export:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
AuthenticationExchange Public API credentials for an access token, or run the partner authorization flow to obtain a token on behalf of a connected company.3 | ||||||
| POST | /v1/auth/token | Exchange Public API credentials for a bearer access token. The client ID and secret are sent base64-encoded in an Authorization header with the Basic prefix. | write | — | Current | |
No scope is needed to call this, but the token it returns carries only the scopes chosen when the credentials were created. This is the server-to-server flow for a company's own credentials, not the partner OAuth flow. Acts onaccess token Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/oauth2/authorize | Start the partner authorization flow, where a connected company signs in and grants an app the chosen scopes, using PKCE. | read | — | Current | |
Used by partner apps acting on behalf of a connected company, not by a company calling its own data. The returned code is exchanged for a token at the OAuth2 token endpoint. Acts onauthorization Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| POST | /v1/oauth2/token/create | Exchange the authorization code from the partner flow for a bearer access token, completing the PKCE exchange. | write | — | Current | |
The returned token carries the scopes the connected company granted during authorization. Acts onaccess token Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
WebhooksCreate, list, read, update, and delete webhook instances that subscribe to payable and settlement events.3 | ||||||
| POST | /v1/experimental/webhooks | Create one or more webhook instances, each linked to a chosen event, with the receiver URL and a signing secret. A company can configure up to 50 instances. | write | webhooks:write | Current | |
Requires the experimental:webhooks:write scope. This area is marked experimental and can change before it settles. Acts onwebhook Permission (capability) webhooks:writeVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| GET | /v1/experimental/webhooks | List the webhook instances configured for the company, ordered by when they were created. | read | webhooks:read | Current | |
Requires the experimental:webhooks:read scope. This area is marked experimental and can change before it settles. Acts onwebhook Permission (capability) webhooks:readVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
| DELETE | /v1/experimental/webhooks/{id} | Delete a webhook instance by its ID. | write | webhooks:write | Current | |
Requires the experimental:webhooks:write scope. This area is marked experimental and can change before it settles. Acts onwebhook Permission (capability) webhooks:writeVersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
MCP serverThe hosted Model Context Protocol endpoint that exposes read access to spend data, suppliers, members, and cost centers to an AI agent.1 | ||||||
| POST | /v1/mcp | Call the hosted Spendesk MCP server, which exposes read access to spend data, suppliers, members, cost centers, and more to an AI agent through the Model Context Protocol. | read | — | Current | |
Read-only. The connecting credentials decide which areas the agent can reach, through the same read scopes the rest of the API uses, such as settlement:read and payable:read. Marketed as Spendesk AI Connect and in beta. Acts onMCP session Permission (capability)None required VersionAvailable since the API’s base version Webhook eventNone Rate limitStandard limits apply SourceOfficial documentation ↗ | ||||||
Spendesk can notify an app or AI agent when something happens in a company, like a payable being created or a settlement being recorded, instead of the app repeatedly asking. Spendesk posts a signed payload to a webhook URL that has been registered for the chosen events.
| Event | What it signals | Triggered by |
|---|---|---|
payables.created | Fires when a payable is created, at version 1. A payable is one expense, such as a card purchase, an invoice, or an expense claim. | /v1/payables/{id} |
payables.prepared | Fires when a payable is prepared, the step where it is enriched and made ready for accounting. | In-app only |
settlements.created | Fires when a settlement is created, at version 1. A settlement records money moving out of the company's Spendesk wallet. | /v1/settlements |
Spendesk limits how fast an app or AI agent can call, through a per-minute request quota measured per company and a separate cap on how many requests run at once. Most responses are also served from a cache, so repeated reads return data that can be up to a set number of seconds old.
Spendesk limits requests per minute per company. A company calling its own data, and a partner calling on behalf of one connected company, each get 50 requests per minute; a partner using OAuth 2.0 gets 250 requests per minute. On top of that, any caller is held to 5 requests running at once. Every response carries x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset headers, and exceeding a limit returns 429 until the number of seconds in x-ratelimit-reset has passed. Most read endpoints are also served from a cache, so repeated reads can return data up to a fixed age, which ranges from 1,200 seconds for payables and members to 604,800 seconds for suppliers and analytical fields.
List endpoints use page-based pagination through the page parameter, which starts at 1, and the pageSize parameter, which defaults to 30 and tops out at 30. Asking for a page past the last one with results returns a 404 rather than an empty list. Listing many payables is done through the asynchronous snapshot endpoints rather than a direct list.
Responses are JSON, except the accounting journal endpoint, which returns CSV. Payable date-range queries are capped at a 31-day window between the from and to dates. A company can configure at most 50 webhook instances, and a webhook signing secret must be at least 32 characters.
The status codes an agent should handle, and what to do about each.
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | BAD_REQUEST_INCORRECT_OR_MISSING_FIELD | The request was malformed, or a required field was missing or invalid. | Read the detail in the errors array, correct the named field, and resend. |
| 401 | AUTHENTICATION_ERROR | Authentication is missing, or the access token is invalid or expired. | Request a fresh access token from the token endpoint and retry with it in the Authorization header. |
| 403 | FORBIDDEN_ERROR | The token is valid but does not carry the scope the endpoint requires. | Issue credentials that include the needed scope, such as settlement:read, then mint a new token. |
| 404 | NOT_FOUND | The resource does not exist, or a paginated request asked for a page beyond the last one with results. | Confirm the ID, and stop paging when a 404 is returned for the next page. |
| 409 | CONFLICT_ERROR | The request conflicts with the current state, such as creating something that already exists. | Refetch the current state, then retry once the conflict is resolved. |
| 422 | IN_PROGRESS | An asynchronous request, such as a payable snapshot, is still being built and is not ready to read. | Wait, then poll the snapshot again until its status is COMPLETE. |
| 429 | TOO_MANY_REQUESTS | A rate limit was exceeded, either the per-minute quota or the cap on requests running at once. | Wait for the number of seconds in the x-ratelimit-reset header before retrying. |
| 500 | INTERNAL_SERVER_ERROR | Something failed on Spendesk's side while handling the request. | Retry after a short delay, and contact Spendesk if it persists. |
Spendesk versions its API through a single path segment, v1, and ships dated changes through release notes. The current generation reached general availability in 2024, when the earlier v0 generation was switched off.
The v1 generation is the current, supported API. It reached general availability in 2024, when it replaced the earlier v0 generation, which stopped working on 29 April 2024. At that point the synchronous payables list was removed in favour of the asynchronous payable snapshot, payable date-range queries were capped at a 31-day window, and the maximum page size across all endpoints was reduced to 30. Newer areas, like suppliers writes, purchase orders, accounting export, and webhooks, ship under an experimental prefix and can change before they settle.
After general availability, Spendesk added experimental webhooks for payable and settlement events, APIs for accounting exports, and later a hosted Model Context Protocol server, Spendesk AI Connect, that exposes read access to spend data to AI assistants. These build on v1 without minting a new version string, and several remain marked experimental.
The v0 generation was the first public API. It carried breaking changes in March 2024 and was switched off entirely on 29 April 2024, after which every call had to move to the v1 path.
Newer areas are marked experimental and can change before they settle.
Spendesk API changelog ↗Bollard AI sits between a team's AI agents and Spendesk. Grant each agent exactly the access it needs, read or write, resource by resource, and every call is checked and logged.