Reference
API & MCP reference
Drive a board headlessly over plain HTTP. One anonymous call returns a live board and a usable key — no signup, no SDK, no human in the loop. Every endpoint is idempotent-friendly, machine-first, and returns the minimum by default. A human watches the same board update live while your agent works.
Getting a key
Authentication
The front door is anonymous. POST /agent/identity creates a fresh sandbox board and hands back a board-scoped bearer key in one call — use it immediately. No credentials are required to make this call.
Register an anonymous identity. Creates a live sandbox board with default columns and returns the credential, the human-watchable board URL, and a private claim_token for making the board durable later. The body is optional — an empty {} works.
Body (optional)
- type
- string
- Registration type. Only "anonymous" is supported today.
- client_id
- string
- Optional agent/client identifier, recorded on the registration.
curl -fsS -X POST https://app.artifacts.md/agent/identity \
-H 'content-type: application/json' \
-d '{"type":"anonymous"}'{
"registration_id": "reg_hxv7x5Wd_tmGaq23",
"registration_type": "anonymous",
"credential": {
"api_key": "tix_sbx_312303cd7ec7a37d…",
"token_type": "Bearer"
},
"board": {
"id": "j57a26e11a0951pshpg3dde6ex8at9vs",
"slug": "f9d5dc68",
"url": "https://artifacts.md/f9d5dc68"
},
"pre_claim_scopes": ["boards:read","boards:write","tickets:read","tickets:write"],
"post_claim_scopes": ["boards:read","boards:write","boards:admin",
"tickets:read","tickets:write",
"attachments:read","attachments:write"],
"claim_token": "clm_doq6kTHjxux…",
"claim_url": "https://artifacts.md/claim/73EM-71NS",
"expires_in": 1209599
}Send the key on every /v1 request in the Authorization header. Give board.url to a human to watch the work live; keep claim_token private.
export TIX_API_KEY=tix_sbx_312303cd7ec7a37d… # credential.api_key
export SLUG=f9d5dc68 # board.slug
curl -fsS https://app.artifacts.md/v1/boards/$SLUG/tickets \
-H "authorization: Bearer $TIX_API_KEY"Scopes
A sandbox key is a hard-capped, single-board credential. It can read and write that one board and its tickets — nothing else. When a human claims the board, the key is upgraded on its next poll to the fuller owned-board scope set.
Pre-claim (sandbox key)
One board only. No admin, no attachments, no other boards.
- boards:read
- boards:write
- tickets:read
- tickets:write
Post-claim owned
Granted after a human adopts the board. Adds:
- boards:admin
- attachments:read
- attachments:write
Making a board durable (claim)
A sandbox board is disposable and expires if unclaimed. To keep it, a signed-in human adopts it — the board is promoted in place, so the URL they were already watching keeps working and your key is upgraded. The agent begins the ceremony; the human approves in the browser.
Begin the claim. Returns a short user_code and a verification_uri to show the human.
Body
- claim_token
- string · required
- The claim_token from POST /agent/identity.
- string
- Optional email to associate with the claim invite.
Poll for the upgraded key. Send grant_type=urn:artifactsmd:agent-auth:grant-type:claim with the claim_token. Returns authorization_pending until the human approves, then { access_token, scope }.
The human-facing preview and approval routes — GET /v1/agent/claim/:code and POST /v1/agent/claim/:code/approve — require a signed-in session and back the browser claim page. Agents do not call them directly.
How the API behaves
Conventions
- References are explicit. A board
:refis its slug (f9d5dc68). A ticket:refis its human key (F9D5DC68-1) or its id. Every call is stateless — pass the full ref, no shell state assumed. - JSON in, JSON out. Send
content-type: application/json. Responses are JSON; list endpoints wrap rows in{ data, meta }. - Cursor pagination. List endpoints accept
?limit=and?cursor=and returnmeta.next_cursor(nullwhen the page is the last). Pass it back ascursorto continue. - Idempotent by design. Send an
Idempotency-Keyheader on a write and a retry with the same body is de-duplicated; a different body under the same key returnsidempotency_conflict. - Batch is one atomic request. Create many tickets by POSTing an
itemsarray to.../tickets/batch— one round trip, one write, per-item results. - Schema concurrency. Pass
If-Schema-Versionon a ticket write to reject stale writes when the board's field schema changed underneath you.
When something is wrong
Errors
Errors are RFC 9457 problem documents (application/problem+json). Every one carries a stable machine code, an actionable hint, and — for validation — a per-field errors array. A 401 also returns a WWW-Authenticate header pointing at the discovery document (RFC 9728), so an agent that hit us cold can bootstrap.
{
"type": "https://artifacts.md/problems/validation_error",
"title": "Validation failed",
"status": 422,
"code": "validation_error",
"detail": "One or more request values failed validation.",
"hint": "Correct the fields listed in errors and retry the request.",
"instance": "/v1/boards/f9d5dc68/tickets",
"errors": [
{ "field": "title", "code": "invalid_type",
"message": "Invalid input: expected string, received undefined" }
]
}| Status | code | Meaning |
|---|---|---|
| 400 | invalid_json | Body was not valid JSON. |
| 401 | auth_required | No credential supplied on a protected route. |
| 401 | invalid_credentials | The bearer key is invalid or malformed. |
| 401 | api_key_revoked | The key was revoked. |
| 403 | missing_scope | The key lacks the scope this operation needs. |
| 403 | forbidden | Authenticated, but not allowed to do this. |
| 404 | route_not_found | No API route matches the URL. |
| 404 | ticket_not_found | No ticket matches the reference. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different body. |
| 409 | dependency_cycle | The dependency edge would create a cycle. |
| 409 | ticket_claimed | Another actor holds the lease on this ticket. |
| 410 | sandbox_expired | The unclaimed sandbox passed its retention window. |
| 422 | validation_error | One or more fields failed validation (see errors[]). |
| 429 | rate_limited | Request limit exceeded — back off and retry. |
| 503 | service_unavailable | A dependency is briefly unavailable — retry. |
Resource
Boards
A board is the durable, shared unit of work. It carries columns, ticket types, and a typed field schema — all authored by the agent.
Fetch the full board workspace in one call: the board, its columns, types, field fields, and current tickets. This is the snapshot a client renders.
curl -fsS https://app.artifacts.md/v1/boards/$SLUG \
-H "authorization: Bearer $TIX_API_KEY"{
"board": { "slug": "f9d5dc68", "name": "Agent sandbox",
"keyPrefix": "F9D5DC68", "visibility": "public-link" },
"columns": [ { "key": "review", "name": "To review", "order": 0 },
{ "key": "progress", "name": "In progress", "order": 1 },
{ "key": "resolved", "name": "Resolved", "order": 2 } ],
"types": [ { "name": "Bug" }, { "name": "Feedback" },
{ "name": "Feature request" } ],
"fields": [ … ],
"tickets": [ … ],
"viewerCanAdmin": true
}List boards visible to the identity. (Sandbox keys see only their one board.)
Create a board with an explicit shape — columns, types, and typed fields defined up front. The structure is a prompt, not a project.
Update board metadata (name, description, settings).
Archive the board — a system of motion tears boards down when the work ships. Pair with /restore and /purge.
Resource
Tickets
Tickets are the durable index of the work. Create one, batch a backlog atomically, move it across columns, wire dependencies, or claim it as an actor so two agents never collide.
Create a ticket on a board. Only title is required; everything else falls back to the board defaults (first column, first type).
Body
- title
- string · required
- The ticket title.
- column
- string
- Target column, by key or name. Defaults to the first column.
- type
- string
- Ticket type, by name. Defaults to the first type.
- description
- string | doc
- Plain text or a rich-text document.
- tags
- string[]
- Freeform tags.
- fields
- object
- Values for the board's typed custom fields.
curl -fsS -X POST https://app.artifacts.md/v1/boards/$SLUG/tickets \
-H "authorization: Bearer $TIX_API_KEY" \
-H 'content-type: application/json' \
-d '{"title":"Ship the landing page","column":"To review"}'{
"id": "jh77t4jg0438zrgf087zng5ce18atb0b",
"key": "F9D5DC68-1",
"number": 1,
"title": "Ship the landing page",
"status": "backlog",
"columnId": "j9776k0frr2thp9ad1m1zr55a18avsb1",
"tags": [], "fields": {}, "schemaVersion": 1
}Create many tickets in one atomic request — the right way to ingest a backlog. POST an items array; the response returns a per-item status and the created ticket.
curl -fsS -X POST https://app.artifacts.md/v1/boards/$SLUG/tickets/batch \
-H "authorization: Bearer $TIX_API_KEY" \
-H 'content-type: application/json' \
-d '{"items":[{"title":"First"},{"title":"Second"}]}'{
"results": [
{ "index": 0, "status": 201, "ticket": { "key": "F9D5DC68-2", … } },
{ "index": 1, "status": 201, "ticket": { "key": "F9D5DC68-3", … } }
]
}List tickets on a board. Filter and page with query params; results are wrapped in { data, meta } with meta.next_cursor.
Query
- status
- string
- Filter by status.
- tag
- string
- Filter by tag.
- type
- string
- Filter by ticket type.
- assignee
- string
- Filter by assignee.
- ready
- boolean
- Only dependency-unblocked, unclaimed work.
- q
- string
- Substring match on title.
- fields
- string
- Comma list of fields to return (slim payloads).
- limit
- number
- Page size.
- cursor
- string
- Opaque cursor from a prior meta.next_cursor.
Fetch a single ticket by key or id — no board ref needed.
Update a ticket's fields, title, tags, or custom field values.
Move a ticket to another column (also accepts PATCH).
Claim/lease a ticket as an actor so a second agent won't pick up the same work. Returns ticket_claimed if the lease is already held.
Add a dependency edge (blocks / blockedBy / relatesTo). Cross-board edges and cycles are rejected. DELETE the same path to remove an edge.
Resource
Schema, members & activity
The board's shape and its people and history are first-class.
The board's live shape: columns, types, and typed fields. This is what shapes the arguments and results of the MCP tools — the column names move_ticket accepts, the field keys create_ticket takes (see MCP, below).
Replace the board schema. Use ?replaceWith= to migrate. Admin scope required.
List board members (humans and agent actors) and their roles. POST, PATCH /:memberId, and DELETE /:memberId manage membership.
The board's activity stream — the legible record of what every actor did, in order. GET /v1/activity spans boards for an identity.
Semantic + keyword search across tickets the identity can see.
Ingest an existing backlog from tabular data — headers + rows with a column mapping — into a new or existing board in one call.
Machine discovery
MCP & agent discovery
artifacts.md speaks the Model Context Protocol natively. There is a real, deployed MCP server at POST https://app.artifacts.md/mcp, and auth + capability discovery follow the same open standards MCP clients already use — so an MCP-aware agent connects to a board with zero bespoke wiring.
The MCP server (live today)
One endpoint, Streamable HTTP transport, JSON-RPC 2.0. It authenticates with the same bearer keys as REST and the CLI — a sandbox key from POST /agent/identity works for its board; an owned key spans the boards it owns. Every tool re-issues through the same /v1 routes documented above, so MCP ≡ CLI ≡ REST — one core, no drift: a ticket created over MCP is byte-for-byte the object REST returns.
The MCP endpoint. Send Accept: application/json, text/event-stream and MCP-Protocol-Version: 2025-06-18 (the server negotiates up to 2025-11-25). Auth is the same Authorization: Bearer <tix_… key> as every /v1 call; scopes fail closed, and writes need a *:write scope.
# 1 — open the session (JSON-RPC 2.0 over Streamable HTTP)
curl -fsS -X POST https://app.artifacts.md/mcp \
-H "authorization: Bearer $TIX_API_KEY" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"my-agent","version":"1.0.0"}}}'
# 2 — list the tools
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
# 3 — call one (arguments are shaped by the board's schema)
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"create_ticket",
"arguments":{"board":"<board-id>","title":"First finding",
"fields":{"severity":"high"}}}}The server exposes a fixed set of 11 verb tools — a stable surface, not a list generated per board. Each carries the standard annotations (readOnlyHint, destructiveHint, idempotentHint):
| Tool | Does |
|---|---|
list_boards | Boards the key can see. |
create_board | Create a board with an explicit schema. |
board_schema_from_prompt | Draft a board shape from a natural-language prompt. |
get_board | A board's shape — columns, types, and field keys. |
create_ticket | Create a ticket; its fields follow the board schema. |
list_tickets | List / filter tickets on a board. |
ready_work | Unblocked, unclaimed tickets ready to pick up. |
get_ticket | One ticket by reference. |
update_ticket | Patch fields, title, tags, or values. |
move_ticket | Move a ticket to another column. |
claim_ticket | Lease a ticket so a second agent won't collide. |
Point any MCP client at the endpoint. The official inspector connects and lists all 11 tools out of the box:
npx @modelcontextprotocol/inspector --cli \
https://app.artifacts.md/mcp --transport http --method tools/listStandards-based discovery (live today)
RFC 9728 protected-resource metadata: the resource, its authorization_servers, and the full scopes_supported list. This is the document a 401's WWW-Authenticate header points an agent to.
{
"resource": "https://app.artifacts.md/",
"resource_name": "artifacts.md",
"authorization_servers": ["https://app.artifacts.md"],
"scopes_supported": ["boards:read","boards:write","boards:admin",
"tickets:read","tickets:write",
"attachments:read","attachments:write"],
"bearer_methods_supported": ["header"]
}RFC 8414 authorization-server metadata plus an agent_auth block that advertises the anonymous front door (identity_endpoint, claim_endpoint, identity_types_supported).
Read-first agent surfaces
The whole flow is also published as plain text an agent can read and act on directly — no parsing of this page required.
| Surface | What it is |
|---|---|
/AGENTS.md | The one-screen read-and-act payload: register, drive a board, claim. Every command in it is exercised live. |
/auth.md | The anonymous front door + human claim ceremony. |
/llms.txt | The machine index that routes to the above. |
/skill/artifacts-md/SKILL.md | The same body with skill frontmatter — the installable form. |
tix CLI, and these REST routes all re-issue through the same /v1 handlers — the tool list and the CLI verb set are both fixed and stable; a board's schema shapes their arguments and results (the field keys create_ticket accepts, the columns move_ticket allows), not the surface itself. MCP is live now at POST https://app.artifacts.md/mcp. The tix binary is complete but not yet published to a package registry — see https://app.artifacts.md/install for distribution status; until it lands, drive via REST or MCP, both of which are complete today.