API and MCP
API reference
Every operation in the Spice Labs REST API, grouped by what it works on. This reference is a copy of the API's own OpenAPI document; inside the dashboard, the API page shows the same reference for your project with a Run it console. To get started, see REST API.
Operations
The Spice Labs API: upload artifacts, run analysis and read the results, and manage the organizations, projects, teams and roles that own them.
Authentication
Every request carries a bearer token in the Authorization header:
Authorization: Bearer <token>
There are three kinds of token:
- A dashboard session is what you hold while signed in to the dashboard. Some operations, such as creating or canceling an API token, accept only a dashboard session.
- An API token is created from the project settings page. It holds a fixed list of permissions, at most those of the person who created it, and acts in the project and organization it was created in.
- A Spice Pass is the upload credential the Spice Labs CLI uses for one project. It lasts 14 days unless another length, up to 367 days, is chosen when it is created.
Each operation's description ends with the kinds of token it accepts.
Send every call to the API host named in servers. The dashboard's own address
serves the dashboard only: an API token or a connected assistant that calls it is
refused with api_host_required.
Connect an AI assistant
Spice Labs runs an MCP server, so an AI assistant or coding agent can read this reference and the Spice Labs CLI reference, and work with your projects as you. Add this address to the assistant as a remote MCP server (some assistants call it a connector or a custom integration):
https://api.spicelabs.io/mcp
In Claude Code, for example:
claude mcp add --transport http spice-labs https://api.spicelabs.io/mcp
The first time the assistant connects, it opens a browser where you sign in to Spice Labs and approve it for the organization you are signed in to. No token or configuration file is needed. You choose what it may do:
- Read the Spice Labs API and CLI documentation.
- Read data in your projects.
- Change and delete data in your projects.
- Create API tokens (lasting one hour) and Spice Passes (lasting 48 hours), with at most your own permissions.
Reading is allowed unless you untick it; changes and credentials only when you tick them. The assistant acts with your permissions and nothing more.
To disconnect an assistant, open Credentials in any project of the organization and choose Disconnect under Connected apps. It stops working immediately, and the API tokens and Spice Passes it created are canceled. Removing a person from an organization disconnects their assistants there too.
Common questions
Where to start for the questions people ask most. Every path below is under
/api/v1/project/{projectId}.
- What should we fix first? List portfolios with
GET /v1/portfolio; the one withis_implicit: trueholds every subject. Its rank,GET /v1/portfolio/{portfolioId}/rank, lists what to fix, most important first, each with its findings, the subjects it reaches and, when known, the version that fixes it (fix_version). Addsummary=truefor small rows with each fix's most important findings. Findings on thevulnaxis are known vulnerabilities; findings on thepqcaxis are cryptography to move away from. - How are we doing, now and over time?
GET /v1/portfolio/{portfolioId}/stateandGET /v1/portfolio/{portfolioId}/history. - What if we fixed these?
POST /v1/portfolio/{portfolioId}/simulate. - What did the latest survey find?
GET /v1/job?topLevel=true&jobType=INVENTORY_SURVEY,RUNTIME_SURVEY&limit=1returns the newest survey. Its reports are thestepswhosestep_typeisREPORT;step_labelnames the kind (pqc-inventory,cbom,composition) and the step'sidis thejobIdforGET /v1/report/{jobId}/summary, then/section/{section}or/search. Filtering jobs byjobType=REPORTfinds only reports made before reports became part of surveys. - What changed between two surveys?
GET /v1/report/comparewith two report ids. - What is in an application?
GET /v1/subjectlists subjects, andGET /v1/subject/{subject}/contentlists what one contains. - Is the data complete and current?
GET /v1/data-health/summary.
Errors
Every error is a JSON object with error (a short message), status (the HTTP status
code, repeated) and code (a machine-readable reason, listed below). Branch on code,
not on the wording of error.
- An
orgIdorprojectIdin the path that is not the one the token was made for answers 403. The project operations that accept any project of the organization answer 404 for one that does not exist. - An operation that reaches a project's data answers 503 while that data is temporarily unreachable, and 502 when the connection to it fails part way. Both are worth sending again after a moment.
Error codes
Each status has a general code, used when nothing more specific applies. The specific codes say more. New codes may be added over time; treat one you do not know as the general code for its status.
| Code | Status | When |
|---|---|---|
invalid_request |
400 | The request was rejected: a body that cannot be read or a value that is not allowed. |
invalid_parameter |
400 | A query, path or header parameter is missing or not valid. parameter names it. |
name_taken |
400 | Another project, team or role in the organization already has this name. |
last_project |
400 | The project is the organization's last one, which cannot be deleted. |
unauthorized |
401 | The token is missing, not valid, or not a kind this operation accepts. |
token_expired |
401 | The token has expired. |
token_revoked |
401 | The token has been canceled. |
session_required |
401 | An API token was sent to an operation that accepts only a dashboard session. |
forbidden |
403 | The token is valid but may not do this, for example a path id it was not made for. |
permission_denied |
403 | The token lacks a permission this operation needs. permission names it. |
api_host_required |
403 | An API token or a connected app called the dashboard's address; send API calls to the API host. |
not_found |
404 | The thing named does not exist or is not visible to the token. |
method_not_allowed |
405 | The path exists but not with this method. The Allow header lists the ones it takes. |
conflict |
409 | The request conflicts with the current state. |
unsupported_media_type |
415 | The body is not sent as JSON. |
rate_limited |
429 | Too many requests from this credential; the API allows 600 a minute for each one. |
internal |
500 | A fault on our side. |
bad_gateway |
502 | The connection to the project's data failed part way. |
provisioning |
503 | The project's data is still being set up. |
starting |
503 | The project's data is starting up. |
unavailable |
503 | The project's data is temporarily unreachable. |
try_again |
503 | The request could not be answered right now. Try again in a few seconds. |
unscored |
503 | The portfolio has not been scored yet. |
rescoring |
503 | The portfolio is being scored again after a change. |
Pagination
Every list takes limit, the page size: 1 to 10000, and 1000 when you give none.
Lists come in two kinds, and each pages one way:
- Lists that grow page by cursor: the activity list
(
GET /api/v1/org/{orgId}/activity), jobs (GET /api/v1/project/{projectId}/v1/job), bundles and data-health runs. The answer'spaginationholdsfirstId,lastId,hasMoreandtotal; passlastIdasafterfor the next page, orfirstIdasbeforefor the previous one, never both. - Computed results page by offset: a portfolio's rank, a subject's contents and a
report section's entries, which do not change while you read them.
offset(default 0) skips that many entries. The answer counts every entry intotal, insidepaginationfor a report section.
Limits
One credential may make 600 requests a minute. Past that the answer is 429 with
code rate_limited; wait a moment and send again.
Versions
Paths carry two kinds of version:
/api/v1at the start of every path is the version of this contract: how organizations and projects are addressed, how tokens work, what an error looks like.- A version after a project id, as in
/api/v1/project/{projectId}/v1/portfolio, is the version of that family of resources.
Each moves on its own. A breaking change to one family arrives as a new v2 path
beside the v1 one, and the v1 path keeps working.
Changes and deprecation
We add to the API without notice, and we never remove or change what is here without telling you first. At any time we may:
- add operations
- add optional parameters to an operation
- add fields to an answer
- add error codes
Anything else is a breaking change: removing or renaming an operation, a parameter or
a field, making an optional parameter required, or changing what a field holds. Before
a breaking change we deprecate what it affects. This reference marks a deprecated
operation and says when it goes, and each call to it answers with a Deprecation
header giving the date it was deprecated, plus a Sunset header with the removal date
once one is set.