Resource API
Configuration is written through the resource API, under
https://app.usespotlight.ai/api/resources, or under /api/resources on your
deployment's address on a self-hosted Spotlight. The
Terraform provider calls the same
endpoints. The web app shows configuration read-only.
Request and response bodies are JSON with camelCase field names. Errors are
application/problem+json.
Credentials
| Credential | Sent as | Acts |
|---|---|---|
| A person's session | the session cookie the web app sets at sign-in | as that person, within their role in each workspace |
| A workspace token | Authorization: Bearer <token> | only within its own workspace, and only as its scopes allow |
A request with neither is refused with 401. See
Roles and permissions for what each
role and scope allows.
Operations
Every resource kind has these six operations, except where a rule below says otherwise. <plural> is the kind's collection
path, from the endpoint catalogue. <holder> is
workspaces/{workspaceId} or teams/{teamId}, depending on what holds the kind.
| Operation | Method and path | Success | Required header |
|---|---|---|---|
| create | POST /api/resources/<holder>/<plural> | 201 | Idempotency-Key |
| read | GET /api/resources/<plural>/{id} | 200 | |
| update | PUT /api/resources/<plural>/{id} | 200 | If-Match |
| delete | DELETE /api/resources/<plural>/{id} | 204 | If-Match |
| import | GET /api/resources/workspaces/{workspaceId}/<plural>/import?address=<address> | 200 | |
| list | GET /api/resources/workspaces/{workspaceId}/<plural> | 200 |
- A read returns every field, with defaults filled in. Create and update return exactly what a read would.
- An update replaces the whole resource. There is no partial update.
- A list returns
{ "items": [...] }, each item as a read would return it, in address order. A list of a team-held kind covers every team in the workspace. - An import returns the resource at an address, as a read would.
Headers
| Header | Direction | Description |
|---|---|---|
Authorization | request | Bearer <token>, for a workspace token. |
Idempotency-Key | request, create | Required. 1 to 255 visible characters. A retry with the same key and the same body within 24 hours returns the resource the first attempt created and creates nothing more. |
If-Match | request, update and delete | Required. The ETag of the version the write is based on, as in "3". |
Spotlight-Api-Version | request and response | Optional on a request. Every response carries the version it was served in. |
Spotlight-Client | request | Optional. The client making the request, recorded with every configuration change: terraform-provider/<version> is recorded as the Terraform provider at that version, web as the web app, and anything else, or nothing, as unknown. |
ETag | response | The resource's version as a strong entity tag, as in "3". |
Idempotent-Replayed | response | true when a create returns an earlier attempt with the same key. |
Spotlight-Workspace-Token | response | The new token, on creating a workspace token. Returned once. |
Spotlight-Pool-Token | response | The token Spotlight issued, on creating a pull pool without one. Returned once. |
Common fields
Every resource representation has these fields besides its own.
| Field | Type | Default | Description |
|---|---|---|---|
id | string | read-only | Issued by Spotlight on create. Never changes. |
version | integer | read-only | Rises with every change. Carried as the ETag. |
address | string | read-only | Names the resource within its workspace. The import operation takes it. |
warnings | array of objects | read-only | Each { path, message, severity }, with severity warning. Each describes a setting Spotlight accepts but cannot honour as written. |
Addresses
| Kind | Address | Example |
|---|---|---|
workspace | slug | acme |
team | key | core |
line | <team key>/<key> | core/delivery |
skill | <team key>/<key> | core/grafana |
harness-profile | <team key>/<key> | core/careful |
integration | <team key>/<integration> | core/acme |
hook | <team key>/<key> | core/migrations-need-a-dba |
secret | name | ACME_API_KEY |
workspace-token | name | ci |
workspace-token-trust | key | github-main |
runner-pool | key | ephemeral |
runner-trust | key | production-cluster |
workspace-runners | the workspace's slug | acme |
replay-candidate | <team key>/<line key>/<station key>/<name> | core/delivery/implementation/Higher effort |
Errors
An error body has these fields.
| Field | Type | Description |
|---|---|---|
type | string | urn:spotlight:problem:<slug>. Stable. |
title | string | A short summary. |
status | integer | The HTTP status. |
detail | string | What went wrong. For invalid, each error as <path>: <message>, one per line. |
errors | array of objects | Present when the request was invalid. Each { path, message, severity }. |
currentVersion | integer | Present on moved-on. The version the resource is at now. |
permission | string | Present on forbidden. The permission the caller lacks. |
| Status | Type | When |
|---|---|---|
400 | bad-request | The request is malformed. |
400 | idempotency-key-required | A create has no Idempotency-Key. |
400 | malformed-if-match | If-Match is not one entity tag such as "3". |
400 | unsupported-api-version | Spotlight-Api-Version names a version this server does not serve. |
401 | unauthenticated | No valid credentials. |
403 | forbidden | The caller's role or token scopes do not allow this request. permission names what it lacks. |
404 | not-found | No such resource that the caller may see. Anything the caller may not see returns 404. |
409 | already-exists | A create names an address already taken. The detail says to import it. |
409 | moved-on | If-Match names a version that is no longer current. |
409 | in-use | The write or delete would remove something another resource or a task still uses. |
409 | conflict | Another conflict with the resource's state. |
422 | invalid | One or more fields are wrong. |
422 | idempotency-key-reused | The Idempotency-Key was used for a different request body. |
428 | precondition-required | An update or delete has no If-Match. |
502 | refused-upstream | An upstream service refused. |
permission is one of configure, manage-secrets, manage-runners,
manage-token-trusts, administer, open-workspaces or plan.
Error paths
An error's path names the attribute it is about, as the Terraform provider
addresses one. An empty path is about the whole resource.
| Form | Example |
|---|---|
| a field | name |
| a list element | stations[2].check |
| a map key that is a plain name | mcpServers[0].env.LINEAR_API_KEY |
| a map key that is not a plain name, quoted | on["task.opened"].to.thread |
Paths use the API's camelCase field names.
API version
The version served is 2026-09-23. Within a version, fields and operations are
only added, never removed, renamed or given a new meaning.
Endpoint catalogue
All paths are under /api/resources.
| Kind | Held by | Create | Read, update, delete | Import and list |
|---|---|---|---|---|
workspace | none | POST /workspaces | /workspaces/{id} | /workspaces/import, /workspaces |
team | workspace | POST /workspaces/{workspaceId}/teams | /teams/{id} | /workspaces/{workspaceId}/teams |
line | team | POST /teams/{teamId}/lines | /lines/{id} | /workspaces/{workspaceId}/lines |
skill | team | POST /teams/{teamId}/skills | /skills/{id} | /workspaces/{workspaceId}/skills |
harness-profile | team | POST /teams/{teamId}/harness-profiles | /harness-profiles/{id} | /workspaces/{workspaceId}/harness-profiles |
integration | team | POST /teams/{teamId}/integrations | /integrations/{id} | /workspaces/{workspaceId}/integrations |
hook | team | POST /teams/{teamId}/hooks | /hooks/{id} | /workspaces/{workspaceId}/hooks |
secret | workspace | POST /workspaces/{workspaceId}/secrets | /secrets/{id} | /workspaces/{workspaceId}/secrets |
workspace-token | workspace | POST /workspaces/{workspaceId}/tokens | /tokens/{id} | /workspaces/{workspaceId}/tokens |
workspace-token-trust | workspace | POST /workspaces/{workspaceId}/token-trusts | /token-trusts/{id} | /workspaces/{workspaceId}/token-trusts |
runner-pool | workspace | POST /workspaces/{workspaceId}/runner-pools | /runner-pools/{id} | /workspaces/{workspaceId}/runner-pools |
runner-trust | workspace | POST /workspaces/{workspaceId}/runner-trusts | /runner-trusts/{id} | /workspaces/{workspaceId}/runner-trusts |
workspace-runners | workspace | POST /workspaces/{workspaceId}/workspace-runners | /workspace-runners/{id} | /workspaces/{workspaceId}/workspace-runners |
replay-candidate | station | POST /lines/{lineId}/stations/{stationKey}/replay-candidates | /replay-candidates/{id}, read and delete only | /workspaces/{workspaceId}/replay-candidates |
Import is the list path followed by /import, except for workspaces.
Rules particular to a kind:
workspaceandteam: see the rules ofspotlight_workspaceandspotlight_team.workspace-token: only a person with theadministerpermission creates, updates or deletes one. An update changes only itsdescription. A delete revokes it.workspace-runners: a workspace has at most one, so its list returns zero or one item.replay-candidate: has no update. Proposing and withdrawing one needplan, and only someone who may read the station's team sees one. It is not exported.
Each kind's fields are on its own page:
| Kind | Page |
|---|---|
workspace | spotlight_workspace |
team | spotlight_team |
line | spotlight_line |
skill | spotlight_skill |
harness-profile | spotlight_harness_profile |
integration | spotlight_integration |
hook | spotlight_hook |
secret | spotlight_secret |
workspace-token | Workspace tokens |
workspace-token-trust | spotlight_workspace_token_trust |
runner-pool | spotlight_runner_pool |
runner-trust | spotlight_runner_trust |
workspace-runners | spotlight_workspace_runners |
replay-candidate | Replay candidates |
Export
| Method and path | Returns |
|---|---|
GET /api/resources/workspaces/{id}/export | { "files": [{ "path", "content" }] }: the whole workspace as a Terraform module. |
GET /api/resources/{collection}/{id}/hcl | { "hcl", "files": [{ "path", "content" }] }: one resource as HCL. |
Both need only permission to read.
The export's files are ordered by path and are the same on every export:
| File | Contents |
|---|---|
main.tf | Every resource, each with an import block beside it, referring to one another. |
variables.tf | A sensitive variable for each value Spotlight never returns, such as secret values and runner pool tokens. |
prompts/<team>/<line>/<station>.md | Each station prompt, read by main.tf with file(). |
judges/<team>/<line>/<station>.md | Each station judge's rubric, read by main.tf with file(). A judge with claims alone has none. |
skills/<team>/<key>/<path> | Each skill file, read by main.tf with file(). |
Workspace tokens are not exported.
The hcl string holds, in order, any variable the resource reads, an import
block that adopts it, and the resource. files holds the prompt, rubric and skill
files it reads. collection is one of workspaces, teams, lines, skills,
harness-profiles, integrations, hooks, secrets, token-trusts,
runner-trusts, runner-pools or workspace-runners.
Configuration changes
GET /api/workspaces/{slug}/configuration-changesReturns every create, update and delete of the workspace's configuration,
newest first. Only a person with the administer permission may read it; for
anyone else the workspace is 404.
| Query parameter | Description |
|---|---|
before | Only changes with a lower seq. |
limit | At most this many. Default 50, at most 200. |
| Field | Type | Description |
|---|---|---|
seq | integer | Rises with every change. Pass it as before to page back. |
resource | string | The kind, as in line or secret. replay is a replay started or stopped, replay-set a replay set's guard of its station's changes turned on or off, replay-search a search started or stopped, replay-check a replay check started, and config-pull-request a search's candidate picked, keyed by its branch. |
resourceId | string | The resource's id. |
key | string | The key or name it had within its team or workspace. |
action | string | created, updated or deleted. |
by.kind | string | person, token, or spotlight for a change Spotlight made following another. |
by.userId | string or null | The person, or for a token the person who issued it. For a token minted through a workspace token trust, the person who defined the trust. |
by.tokenId | string or null | The workspace token. |
by.trust | string or null | The workspace token trust the token was minted through. |
client | string or null | The client, from Spotlight-Client. |
details | object | What the kind records beyond the action. A secret records its value version and bound hosts, never its value. |
at | string | When, as an ISO 8601 time. |
Expression checks
POST /api/expressions/check and GET /api/expressions/positions take the
same credentials and return the same problems as the resource API. See
Check API.