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.

Base URLhttps://app.artifacts.mdAuthAuthorization: Bearer <key>Mediaapplication/json

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.

POST/agent/identityNo auth

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.

POST/agent/identity/claimNo auth

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.
email
string
Optional email to associate with the claim invite.
POST/oauth/tokenNo auth

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 :ref is its slug (f9d5dc68). A ticket :ref is 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 return meta.next_cursor (null when the page is the last). Pass it back as cursor to continue.
  • Idempotent by design. Send an Idempotency-Key header on a write and a retry with the same body is de-duplicated; a different body under the same key returns idempotency_conflict.
  • Batch is one atomic request. Create many tickets by POSTing an items array to .../tickets/batch — one round trip, one write, per-item results.
  • Schema concurrency. Pass If-Schema-Version on 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" }
  ]
}
StatuscodeMeaning
400invalid_jsonBody was not valid JSON.
401auth_requiredNo credential supplied on a protected route.
401invalid_credentialsThe bearer key is invalid or malformed.
401api_key_revokedThe key was revoked.
403missing_scopeThe key lacks the scope this operation needs.
403forbiddenAuthenticated, but not allowed to do this.
404route_not_foundNo API route matches the URL.
404ticket_not_foundNo ticket matches the reference.
409idempotency_conflictIdempotency-Key reused with a different body.
409dependency_cycleThe dependency edge would create a cycle.
409ticket_claimedAnother actor holds the lease on this ticket.
410sandbox_expiredThe unclaimed sandbox passed its retention window.
422validation_errorOne or more fields failed validation (see errors[]).
429rate_limitedRequest limit exceeded — back off and retry.
503service_unavailableA 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.

GET/v1/boards/:refBearer key

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
}
GET/v1/boardsBearer key

List boards visible to the identity. (Sandbox keys see only their one board.)

POST/v1/boardsBearer key

Create a board with an explicit shape — columns, types, and typed fields defined up front. The structure is a prompt, not a project.

PATCH/v1/boards/:refBearer key

Update board metadata (name, description, settings).

POST/v1/boards/:ref/archiveBearer key

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.

POST/v1/boards/:ref/ticketsBearer key

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
}
POST/v1/boards/:ref/tickets/batchBearer key

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", … } }
  ]
}
GET/v1/boards/:ref/ticketsBearer key

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.
GET/v1/tickets/:refBearer key

Fetch a single ticket by key or id — no board ref needed.

PATCH/v1/tickets/:refBearer key

Update a ticket's fields, title, tags, or custom field values.

POST/v1/tickets/:ref/moveBearer key

Move a ticket to another column (also accepts PATCH).

POST/v1/tickets/:ref/claimBearer key

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.

POST/v1/tickets/:ref/dependenciesBearer key

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.

GET/v1/boards/:ref/schemaBearer key

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).

PUT/v1/boards/:ref/schemaBearer key

Replace the board schema. Use ?replaceWith= to migrate. Admin scope required.

GET/v1/boards/:ref/membersBearer key

List board members (humans and agent actors) and their roles. POST, PATCH /:memberId, and DELETE /:memberId manage membership.

GET/v1/boards/:ref/activityBearer key

The board's activity stream — the legible record of what every actor did, in order. GET /v1/activity spans boards for an identity.

POST/v1/searchBearer key

Semantic + keyword search across tickets the identity can see.

POST/v1/import/sheetBearer key

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.

POST/mcpBearer key

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):

ToolDoes
list_boardsBoards the key can see.
create_boardCreate a board with an explicit schema.
board_schema_from_promptDraft a board shape from a natural-language prompt.
get_boardA board's shape — columns, types, and field keys.
create_ticketCreate a ticket; its fields follow the board schema.
list_ticketsList / filter tickets on a board.
ready_workUnblocked, unclaimed tickets ready to pick up.
get_ticketOne ticket by reference.
update_ticketPatch fields, title, tags, or values.
move_ticketMove a ticket to another column.
claim_ticketLease 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/list

Standards-based discovery (live today)

GET/.well-known/oauth-protected-resourceNo auth

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"]
}
GET/.well-known/oauth-authorization-serverNo auth

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.

SurfaceWhat it is
/AGENTS.mdThe one-screen read-and-act payload: register, drive a board, claim. Every command in it is exercised live.
/auth.mdThe anonymous front door + human claim ceremony.
/llms.txtThe machine index that routes to the above.
/skill/artifacts-md/SKILL.mdThe same body with skill frontmatter — the installable form.
One core, three surfaces. The MCP server, the single-binary 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.