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 with is_implicit: true holds 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). Add summary=true for small rows with each fix's most important findings. Findings on the vuln axis are known vulnerabilities; findings on the pqc axis are cryptography to move away from.
  • How are we doing, now and over time? GET /v1/portfolio/{portfolioId}/state and GET /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=1 returns the newest survey. Its reports are the steps whose step_type is REPORT; step_label names the kind (pqc-inventory, cbom, composition) and the step's id is the jobId for GET /v1/report/{jobId}/summary, then /section/{section} or /search. Filtering jobs by jobType=REPORT finds only reports made before reports became part of surveys.
  • What changed between two surveys? GET /v1/report/compare with two report ids.
  • What is in an application? GET /v1/subject lists subjects, and GET /v1/subject/{subject}/content lists 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 orgId or projectId in 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's pagination holds firstId, lastId, hasMore and total; pass lastId as after for the next page, or firstId as before for 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 in total, inside pagination for 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/v1 at 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.