API and keys
Last updated 7 November 2026
For engineers who script against Spellwarden from their own tools or pipelines. At the end you will have created an API key with the access the script needs, made a first request to the v1 API, and know which route to call, what an error means and how fast you may call.
Plans: Team and Enterprise
Who the API is for
The v1 API is for scripts, dashboards of your own and pipelines that read or change the data of one Spellwarden organisation: list skills and MCP servers, start scans, read findings, start eval runs, read runtime traces, export policy packs. It is on the Team and Enterprise plans, and a trial of Team counts. Some routes need Enterprise, such as everything about the private registry.
A key reaches exactly one organisation. Someone who works in several organisations creates a key in each. The API answers in JSON, and every route lives under one base address, shown below. The same tree is also what the Claude connector and other MCP clients use, acting as the person who approved them, as the connector guide describes.
Keys are created and revoked in the dashboard. An API key can list the keys of its organisation and cannot create or revoke one, so a script cannot widen its own access.
Key types
A key has one access level, chosen when it is created and never changed afterwards. There are four. An organisation can have 20 read or write keys together, 200 CI keys and 20 ingest keys at the same time; each kind counts against its own limit. Revoked keys do not count.
| Access on the page | What the key can reach | Typical use |
|---|---|---|
| Read | The organisation and its keys, and everything that reads data: skills, servers, findings, registry, policy, evals, exports, runtime, repositories and the catalog. Scopes: org:read, apikey:read and data:read. | Reports, dashboards, audits |
| Read and write | Everything a read key reaches, and the routes that change data: start scans and imports, change a finding's status, build exports, start eval runs, report manual findings. It adds the scope data:write. | Automation that scans and triages |
| CI (service account) | Only the three CI routes and the organisation read. Scopes: org:read and ci:run. It can report scans and nothing else. | A pipeline that cannot present an identity token |
| Ingest | Nothing in the v1 API. It sends OpenTelemetry records to otel.spellwarden.ai and cannot read or change anything. | The agents or collector that send runtime traces |
No key holds the policy:admin scope, which only owners and managers hold in the dashboard. Accepting a critical or high finding, changing the registry's policy or an organisation rule, and promoting a finding to a rule are therefore dashboard actions. A request for one of them with a key is answered permission_denied. A write route called with a read key is answered write_access_required.
Ingest keys are for runtime traces and have their own guide: Runtime traces and sessions. An ingest key sent to a v1 route or the MCP endpoint is refused with ingest_credential_not_allowed.
Create and revoke a key
Only owners and managers can create or revoke keys. Other roles can see the list. Creating a key in an organisation that is read-only for lack of billing is refused until billing is added.
- Open Settings and then API keys in the dashboard.
- Select Create API key. Give the key a name of 1 to 64 characters that tells you where it is used, and choose the access: Read, Read and write, CI (service account) or Ingest.
- Select Create API key in the dialog. The result shows the key once, with the note that it will not be shown again. Copy it, then select Done.
- Store the key in the secret store of the tool that will use it, such as the secrets of your pipeline.
A key starts with swk_ and is 44 characters long. The fixed prefix lets secret scanners recognise a leaked key. Spellwarden keeps a hash of the key, the prefix and the last four characters, never the value, so it cannot show the key again. If a key is lost, create another and revoke the old one.
The list shows each key's name, access, last four characters, creator, creation date and last use. The last-use time is updated at most once every 60 minutes, so a key used a minute ago may still show an older time.
To revoke a key, select Revoke next to it and confirm. Scripts that use it stop working at once, with api_key_revoked. The key stays in the list under Revoked keys. Creating and revoking are written to the audit log. If creating a key answers api_key_limit, the organisation has reached the limit of that key type; revoke one that is no longer used.
Authenticate a request
The base address of the API is https://dash.spellwarden.ai/api/v1. Every route in the table below is relative to it. Send the key in the x-api-key header, or as a bearer token in the Authorization header. When both are present, x-api-key is used.
A first request lists the organisation your key reaches. Keep the key in an environment variable rather than in the command:
curl -H "x-api-key: $SPELLWARDEN_API_KEY" https://dash.spellwarden.ai/api/v1/orgsThe answer has an orgs list with the organisation's URL name (slug), name, plan, billing state and whether it can be reached, and a caller object that says the caller is an API key and its access. The URL name is the value you put in the paths below where they show {slug}. To read one organisation, call /api/v1/orgs/<your-organisation>: it adds the member count and whether single sign-on is on.
The same call without a key shows what a refusal looks like. This was run once without any credential, and the answer is unchanged apart from its request id being shortened to the field's name:
curl -i https://dash.spellwarden.ai/api/v1/orgs
HTTP/2 401
content-type: application/json
www-authenticate: Bearer resource_metadata="https://dash.spellwarden.ai/.well-known/oauth-protected-resource"
{"error": "api_key_missing", "message": "This request has no API key.", "request_id": "<request id>"}The v1 routes
The routes below are those registered in the backend's v1 tree. Paths are relative to the base address; {slug} is the organisation's URL name and {id} the identifier a list route returned. The scope is what the key needs: a read key holds org:read, apikey:read and data:read, a read and write key adds data:write, and a CI key holds org:read and ci:run. Routes that need ci:run reject every other key with ci_key_required.
A list route answers one page and a cursor; pass the cursor back as the cursor query parameter to get the next page. The field is called cursor or next_cursor depending on the route. Every call of a read-only organisation is refused with subscription_required, reads included.
Organisation and keys
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs | Lists the organisation the key reaches, with its plan and billing state, and who is calling. | org:read |
| GET | /orgs/{slug} | Shows the organisation: plan, billing state, member count and whether single sign-on is on. | org:read |
| GET | /orgs/{slug}/apikeys | Lists the organisation's keys with their access, last four characters and dates. Never a key's value. | apikey:read |
Skills
| Method | Path | What it does | Scope |
|---|---|---|---|
| POST | /orgs/{slug}/skills/scans | Starts a scan of a skill from a source you name. Answers 202 with the skill and the queued scan. | data:write |
| POST | /orgs/{slug}/skills/imports | Starts an import of the skills in a marketplace file, one scan for each skill it finds. Answers 202 with the scan. | data:write |
| GET | /orgs/{slug}/skills | Lists the organisation's skills, with filters for curated, quarantined and source. | data:read |
| GET | /orgs/{slug}/skills/{id} | Shows one skill with its latest scan. | data:read |
| GET | /orgs/{slug}/skills/{id}/scans | Lists the scans of one skill, newest first. | data:read |
| POST | /orgs/{slug}/skills/{id}/scans | Scans a skill again. | data:write |
MCP servers
| Method | Path | What it does | Scope |
|---|---|---|---|
| POST | /orgs/{slug}/mcp-servers | Adds a server by its endpoint, its repository or its official registry name, and starts its first scan. | data:write |
| POST | /orgs/{slug}/mcp-servers/imports | Starts an import of the servers in a .mcp.json file. Answers 202 with the scan. | data:write |
| GET | /orgs/{slug}/mcp-servers | Lists the servers with the summary counts. | data:read |
| GET | /orgs/{slug}/mcp-servers/{id} | Shows one server. | data:read |
| GET | /orgs/{slug}/mcp-servers/{id}/scans | Lists the scans of one server. | data:read |
| POST | /orgs/{slug}/mcp-servers/{id}/scans | Scans a server again. | data:write |
| PUT | /orgs/{slug}/mcp-servers/{id}/tools | Sets the tool list of a server by pasting it, for a server Spellwarden cannot probe. | data:write |
| GET | /orgs/{slug}/mcp-servers/{id}/migration-report | Shows the migration report: classification, findings and the ordered checklist. | data:read |
Scans and findings
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/scans/{id} | Shows one scan and its status; poll it after a route answered 202. | data:read |
| GET | /orgs/{slug}/findings | Lists findings, newest scan first, with filters such as severity, status, target and area. | data:read |
| PATCH | /orgs/{slug}/findings/{id} | Accepts a finding with a note, reopens it or marks a manual finding fixed. Accepting a critical or high finding is refused for a key. | data:write |
| POST | /orgs/{slug}/findings | Reports a manual finding against a skill or server. | data:write |
Registry
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/registry | Shows the registry, its policy and a page of admitted entries, with the addresses of the registry API and the bundle. | data:read |
| POST | /orgs/{slug}/registry/marketplace | Starts building the Claude Code marketplace bundle. Answers 202. | data:write |
| GET | /orgs/{slug}/registry/marketplace | Shows the latest bundle: its status and, when it is ready, a download link. | data:read |
| GET | /orgs/{slug}/registries/{name}/v0/servers | Lists the admitted servers in the shape of the open MCP registry API. | data:read |
| GET | /orgs/{slug}/registries/{name}/v0/servers/{serverName} | Shows one admitted server by its full name, URL-encoded. | data:read |
| GET | /orgs/{slug}/registry/allow | Lists the allow entries of the registry. Adding and removing happen in the dashboard. | data:read |
Rules, policy packs and the trust score
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/rules | Lists the rules that apply, the Spellwarden rules and your organisation's own. | data:read |
| GET | /orgs/{slug}/policy-packs | Lists the policy packs with their latest version. | data:read |
| GET | /orgs/{slug}/policy-packs/{id} | Shows one policy pack. | data:read |
| GET | /orgs/{slug}/policy-packs/{id}/versions/{v} | Shows one version of a policy pack. | data:read |
| POST | /orgs/{slug}/policy-packs/{id}/versions/{v}/exports | Builds an export of a published pack version for a target and returns a download link. | data:write |
| GET | /orgs/{slug}/trust | Shows the trust score and its parts. | data:read |
Evals and exports
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/eval-suites | Lists the eval suites with their calibration status. | data:read |
| POST | /orgs/{slug}/eval-runs | Starts an eval run. Needs an Idempotency-Key header with a new value for each run: the same key replays the same run. | data:write |
| GET | /orgs/{slug}/eval-runs | Lists eval runs. | data:read |
| GET | /orgs/{slug}/eval-runs/{id} | Shows one eval run and its progress. | data:read |
| GET | /orgs/{slug}/eval-runs/{id}/trace | Returns a link to the run's trace. | data:read |
| GET | /orgs/{slug}/eval-exports/sources | Lists what can be exported. | data:read |
| POST | /orgs/{slug}/eval-exports | Builds an eval export. | data:write |
| GET | /orgs/{slug}/eval-exports | Lists eval exports. | data:read |
| GET | /orgs/{slug}/eval-exports/{id} | Shows one export with a fresh download link while it is ready. | data:read |
| POST | /orgs/{slug}/eval-exports/{id}/push | Pushes an export to a destination. | data:write |
| GET | /orgs/{slug}/export-destinations | Lists the export destinations. | data:write |
| POST | /orgs/{slug}/export-destinations/{id}/check | Checks a destination and stores the result on it. | data:write |
Runtime, repositories and the catalog
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/runtime/summary | Summarises what agents used in a period. | data:read |
| GET | /orgs/{slug}/runtime/inventory | Lists servers, tools, skills, models or agents seen at runtime; the kind query parameter picks which. | data:read |
| GET | /orgs/{slug}/runtime/sessions | Lists sessions, with filters for service and agent. | data:read |
| GET | /orgs/{slug}/runtime/sessions/{session_key} | Shows one session. Never its stored content. | data:read |
| GET | /orgs/{slug}/repositories | Lists repositories that report scans from CI. | data:read |
| GET | /orgs/{slug}/repositories/{id} | Shows one repository with its latest scans and recent requests. | data:read |
| GET | /orgs/{slug}/repositories/{id}/scans | Lists a repository's scans; the kind query parameter picks default_branch, request or reported. | data:read |
| GET | /orgs/{slug}/catalog | Shows the catalog: its entries, collections and categories. | data:read |
| GET | /orgs/{slug}/catalog/entries/{entry_id} | Shows one catalog entry. | data:read |
| GET | /orgs/{slug}/catalog/installs | Lists the catalog entries the organisation installed. | data:read |
CI and the MCP endpoint
| Method | Path | What it does | Scope |
|---|---|---|---|
| GET | /orgs/{slug}/ci/policy | Returns the organisation's CI policy for a run: which rules or pack to apply and how the registry check runs. | ci:run |
| POST | /orgs/{slug}/ci/scans | Uploads the report of a pipeline run. | ci:run |
| POST | /orgs/{slug}/ci/registry-check | Asks whether the private registry admits the skills, servers, commands, plugins and marketplaces of a change. Enterprise. | ci:run |
| POST | /mcp | The MCP server. A tool that changes data needs a read and write key. See the section on the MCP endpoint. | per tool |
Errors
An error answers with a JSON body of three fields: error, a short code that a program can match on; message, one plain sentence; and request_id. Some codes add fields, such as the name of the field that was wrong. The same id is in the X-Request-Id response header. Match on the code, not on the message, and quote the request id when you write to hello.invalid [at] spellwarden [dot] ai about a failed call.
| Status | Code | What it means |
|---|---|---|
| 400 | invalid_json, invalid_request | The body is not a JSON object, or a field or query value is not acceptable. The message names it. |
| 401 | api_key_missing | The request carries no key. |
| 401 | api_key_invalid | The value is not a key of this service. |
| 401 | api_key_revoked | The key was revoked, or its organisation was deleted. |
| 402 | subscription_required | The organisation is read-only until billing is added. Every v1 call is refused, reads included. |
| 403 | permission_denied | The key's access does not include what the route needs. |
| 403 | write_access_required | The route changes data and the key is a read key. |
| 403 | ci_key_required | A CI route was called with a key that is not a CI key or CI token. |
| 403 | ci_credential_not_allowed, ingest_credential_not_allowed | A CI or ingest credential was sent to the MCP endpoint, or an ingest key to a v1 route. |
| 403 | plan_required | The plan does not include the feature, for example the private registry on Team. |
| 403 | org_suspended, org_pending_deletion | The organisation cannot be used in its current state. |
| 404 | org_not_found | The URL name is unknown, or it is not the organisation of this key. |
| 404 | not_found | The thing named by the path was not found in this organisation. |
| 405 | method_not_allowed | The path exists for other methods. The Allow header lists them. |
| 409 | scan_in_progress, api_key_limit | The target already has a scan running, or the key limit is reached. Other 409 codes name the limit or the state that blocks the call. |
| 413 | payload_too_large | The request body is over the route's size limit. |
| 429 | rate_limited | Too many requests. The Retry-After header gives the seconds to wait. |
| 500 | internal | Something went wrong on the service side. Try again, and quote the request id if it persists. |
Rate limits
The API allows 300 requests a minute for each key. The count starts after the key is checked, so a refused key is not counted, and each key has its own count: two keys of one organisation do not share it. When a key is over the limit the answer is 429 with the code rate_limited and a Retry-After header with the number of seconds until the window ends. Wait that long before the next call.
Some routes have a lower limit of their own, counted for the organisation or the key:
| Route | Limit |
|---|---|
| Every scan start (scans, imports, rescans, registry jobs) | 500 a day for the organisation |
| Reporting manual findings | 200 a day for the organisation and 50 a day for each key |
| Building a policy pack export | 100 an hour for the organisation |
| Building an eval export | 60 an hour for the organisation |
| Pushing an eval export | 20 an hour for the organisation |
| Checking an export destination | 30 an hour for the organisation |
| Opening an eval run's trace | 60 an hour for each caller address |
The CI routes and ingest have their own limits, described in their guides. Eval runs also count against the organisation's monthly allowance, which the evals guide covers.
CI tokens
A CI token is the credential of connected CI. It lasts 15 minutes, starts with swt_, and is minted by Spellwarden from the identity token that the code host gives the pipeline run. The pipeline exchanges one for the other at the start of a run, so it never stores a secret. A CI token is never created or listed under Settings, and it has no entry in the key list.
The token is bound to one repository and to the one pipeline run it was minted for, and it holds the same two scopes as a CI key: org:read and ci:run. It reaches the three CI routes of the table above and the organisation read, and nothing else. On any other route it is refused with permission_denied, on the MCP endpoint with ci_credential_not_allowed, and on a repository it is not bound to with scope_mismatch. The GitHub Action and the GitLab component do the exchange for you.
A CI key is the fallback for a pipeline that cannot present an identity token. Create it with the access CI (service account); it holds the same scopes and reaches the same routes, and the value is a swk_ key like any other. The setup of both, and what a connected run does, is in Scan in CI with GitHub or GitLab.
The reverse also holds: a read or write key, or a connector, that calls a CI route is refused with ci_key_required. CI credentials do one job, and the keys that read data cannot be used to upload scans as a pipeline.
The MCP endpoint
Spellwarden's MCP server is part of the same tree, at https://dash.spellwarden.ai/api/v1/mcp. It takes POST requests only; a GET is answered 405. Claude and other MCP clients usually connect with a sign-in and an approval, and the connector guide covers that route: Spellwarden connector for Claude.
A client that cannot sign in can use a read or a read and write key instead. The tools that only read work with either; a tool that changes data needs a read and write key and otherwise answers write_access_required. For Claude Code the dashboard's Connect an MCP client page shows this command, to run with your key in place of YOUR_API_KEY:
claude mcp add --transport http spellwarden https://dash.spellwarden.ai/api/v1/mcp --header "Authorization: Bearer YOUR_API_KEY"CI keys, CI tokens and ingest keys are refused at this endpoint.