Skip to content

[00] API reference · v1

Put agents to work from your own systems.

Create cards, start runs and read what the agent produced. JSON over HTTPS, scoped keys, and signed webhooks when something changes.

Base URLhttps://dispatch.asrar.software/api/v1
Endpoints
8
Scopes
5
Webhook events
6
Requests / min / key
120
Getting started

Introduction

The API speaks the same language as the board. A card is a task for an agent, a run is one execution of it, and an artifact is what the run produced — a document, a table, JSON or an email draft, with its sources.
  • Requests and responses are JSON. Send Content-Type: application/json with bodies.
  • Timestamps are ISO 8601 in UTC; ids are UUIDs; costs are in euro cents.
  • Every response carries an X-Request-Id header. Include it when you contact support.
  • The API is part of the Pro plan. Everything an agent sends outside Dispatch still waits for a person to approve it.

Resources

  1. CardA task on a boardPOST /cardsGET /cards/{id}
  2. RunOne execution by an agentPOST /cards/{id}/runsGET /runs/{id}
  3. ArtifactWhat the run producedGET /runs/{id}/artifacts

Side effects proposed by a run wait for an approval in Dispatch.

Authentication

Authenticate with a workspace API key in the Authorization header. Owners and admins create keys in Tools & integrations → API keys.
  • Keys start with dsp_ and are shown once. Dispatch stores a hash and the prefix only, so a lost key is replaced, never recovered.
  • A key acts as the member who created it, with their current role. If they leave the workspace, the key stops working.
  • Revoking a key takes effect on the next request. Keep keys on your server — never in a browser or a mobile app.

Scopes

ScopeAllows
cards:readList boards, task types and cards; read a card.
cards:writeCreate cards on any board of the workspace.
runs:readRead a run’s status, timings, tokens and cost.
runs:writeStart a run on a card (counts toward usage).
artifacts:readRead the artifacts a run produced, with citations.

A write scope includes reading what it writes: cards:write can read cards. A request without the right scope gets a 403 that names the missing one in details.requiredScopes.

Authenticated request
curl "https://dispatch.asrar.software/api/v1/boards" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
401 Unauthorized
{
  "message": "This API key was revoked.",
  "code": "UNAUTHORIZED",
  "requestId": "req_x9Tq4mB2cW7k"
}

Quickstart

Three calls: create a card and start its run, wait for the run, then read the artifact. In production, replace the polling with a run.completed webhook.
# 1 · Create a card and start its run
curl -X POST "https://dispatch.asrar.software/api/v1/cards" \
  -H "Authorization: Bearer $DISPATCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "boardId": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
    "title": "Weekly competitor digest",
    "taskType": "research_brief",
    "inputs": { "topic": "What changed in Linear and Height pricing this week?" },
    "run": true
  }'

# 2 · Read the run (or subscribe to run.completed)
curl "https://dispatch.asrar.software/api/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"

# 3 · Fetch what the agent produced
curl "https://dispatch.asrar.software/api/v1/runs/$RUN_ID/artifacts" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
  1. [01]CreatePOST /cards with the task type’s inputs and run: true.
  2. [02]FollowGET /runs/{id} until it leaves queued and running.
  3. [03]CollectGET /runs/{id}/artifacts for the result and its citations.

Rate limits

Limits apply per key, in one-minute windows: 120 requests, of which 30 may create cards or start runs.
HeaderMeaning
X-RateLimit-LimitRequests allowed in the current window.
X-RateLimit-RemainingRequests left before the window resets.
X-RateLimit-ResetWhen the window resets, in Unix seconds.
Retry-AfterOn 429 only: seconds to wait before retrying.

Runs have their own limits

Starting a run also counts toward the workspace’s monthly runs and its concurrent-run slots, exactly as in the app. A run that can’t start right away waits in Queued.
Response headers
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790524860
Retry-After: 17
X-Request-Id: req_Qm8x1Lr0VbN3

Errors

Errors use standard HTTP status codes and one JSON shape: a readable message, a stable code to branch on, optional details, field issues for validation errors, and the requestId.
StatusWhen
400VALIDATION_ERRORA parameter or input is invalid. issues lists each field.
401UNAUTHORIZEDThe key is missing, malformed, revoked, or its creator left the workspace.
402PLAN_LIMITThe workspace is not on Pro, or a plan limit is reached (runs this month…).
403FORBIDDENThe key lacks a scope (details.requiredScopes) or its creator’s role is too low.
404NOT_FOUNDThe resource does not exist in this workspace.
409CONFLICTThe card is already running.
422UNPROCESSABLEThe card can’t run yet (incomplete inputs, archived board…).
429RATE_LIMITEDToo many requests for this key. Wait Retry-After seconds.
400 Bad Request
{
  "message": "Some inputs are invalid",
  "code": "VALIDATION_ERROR",
  "issues": [
    {
      "path": "inputs.topic",
      "message": "Topic or question is required"
    }
  ],
  "requestId": "req_7Hc2pWm9sK1d"
}

Pagination

Lists return { object: "list", data, hasMore, nextCursor }, newest first. Pass nextCursor as cursor to get the next page, and limit (1–100, default 20) to size it.
JavaScript
let cursor = null;
const cards = [];
do {
  const qs = new URLSearchParams({ limit: '100', ...(cursor ? { cursor } : {}) });
  const page = await fetch(`https://dispatch.asrar.software/api/v1/cards?${qs}`, {
    headers: { Authorization: `Bearer ${process.env.DISPATCH_API_KEY}` },
  }).then((r) => r.json());
  cards.push(...page.data);
  cursor = page.hasMore ? page.nextCursor : null;
} while (cursor);
WorkspaceGET/api/v1/boards

List boards

Boards of the workspace, to know where cards can be created.
Scopescards:read→ 200
Request
curl "https://dispatch.asrar.software/api/v1/boards" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "id": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
      "object": "board",
      "name": "Growth research",
      "description": "Market and competitor research for the growth team.",
      "archived": false,
      "createdAt": "2026-08-02T10:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
GET/api/v1/task-types

List task types

Task types and the inputs each one expects (inputs.fields).
Scopescards:read→ 200
Request
curl "https://dispatch.asrar.software/api/v1/task-types" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "key": "research_brief",
      "object": "task_type",
      "id": "4b1d…",
      "name": "Research brief",
      "description": "A sourced brief on a topic or question.",
      "outputKind": "document",
      "approvalPolicy": "side_effects",
      "available": true,
      "inputs": {
        "version": 1,
        "fields": [
          {
            "key": "topic",
            "type": "textarea",
            "label": "Topic or question",
            "required": true
          }
        ]
      }
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
CardsGET/api/v1/cards

List cards

Cards of the workspace, newest first, with cursor pagination.
Scopescards:read→ 200

Query parameters

  • boardIduuid

    Only cards of this board.

  • statusstring

    Only cards in this status.

    draftreadyqueuedrunningawaiting_approvalreviewdonefailedcancelled
  • limitinteger

    Page size, 1–100 (default 20).

  • cursoruuid

    nextCursor of the previous page.

Request
curl "https://dispatch.asrar.software/api/v1/cards?status=review&limit=20" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "id": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
      "object": "card",
      "title": "Competitor pricing — Linear vs Height",
      "description": null,
      "status": "ready",
      "priority": "normal",
      "board": {
        "id": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
        "name": "Growth research"
      },
      "taskType": {
        "key": "competitor_summary",
        "name": "Competitor summary"
      },
      "inputs": {
        "urls": [
          "https://linear.app/pricing",
          "https://height.app/pricing"
        ],
        "focus": "pricing"
      },
      "labels": [
        "pricing"
      ],
      "assignee": null,
      "dueAt": null,
      "runCount": 0,
      "lastRunId": null,
      "totalCostCents": 0,
      "createdAt": "2026-09-27T09:14:03.000Z",
      "updatedAt": "2026-09-27T09:14:03.000Z",
      "url": "https://dispatch.asrar.dev/app/asrar-studio/boards/b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61?card=7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40"
    }
  ],
  "hasMore": true,
  "nextCursor": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40"
}
POST/api/v1/cards

Create a card

Adds a card to a board’s Backlog. Inputs are validated against the task type, exactly as in the app.
Scopescards:write→ 201

Body

  • boardIduuidrequired

    The board to add the card to.

  • titlestringrequired

    Up to 200 characters.

  • taskTypestring

    Task type key, e.g. research_brief (or taskTypeId).

  • taskTypeIduuid

    Task type id, instead of taskType.

  • inputsobject

    Values for the task type’s input fields.

  • descriptionstring

    Context for the agent and your team (Markdown).

  • prioritystring

    Default normal.

    lownormalhighurgent
  • dueAtdate-time

    ISO 8601 with offset.

  • runboolean

    Start a run right away. Needs the runs:write scope.

Good to know

  • A card.created webhook event is sent to subscribed endpoints.
Request
curl -X POST "https://dispatch.asrar.software/api/v1/cards" \
  -H "Authorization: Bearer $DISPATCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "boardId": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
    "title": "Competitor pricing — Linear vs Height",
    "taskType": "competitor_summary",
    "inputs": {
      "urls": [
        "https://linear.app/pricing",
        "https://height.app/pricing"
      ],
      "focus": "pricing"
    }
  }'
Response
{
  "id": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
  "object": "card",
  "title": "Competitor pricing — Linear vs Height",
  "description": null,
  "status": "ready",
  "priority": "normal",
  "board": {
    "id": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
    "name": "Growth research"
  },
  "taskType": {
    "key": "competitor_summary",
    "name": "Competitor summary"
  },
  "inputs": {
    "urls": [
      "https://linear.app/pricing",
      "https://height.app/pricing"
    ],
    "focus": "pricing"
  },
  "labels": [
    "pricing"
  ],
  "assignee": null,
  "dueAt": null,
  "runCount": 0,
  "lastRunId": null,
  "totalCostCents": 0,
  "createdAt": "2026-09-27T09:14:03.000Z",
  "updatedAt": "2026-09-27T09:14:03.000Z",
  "url": "https://dispatch.asrar.dev/app/asrar-studio/boards/b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61?card=7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40"
}
GET/api/v1/cards/{cardId}

Retrieve a card

One card with its status, inputs, labels and last run.
Scopescards:read→ 200

Path parameters

  • cardIduuidrequired

    The card id.

Request
curl "https://dispatch.asrar.software/api/v1/cards/$CARD_ID" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "id": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
  "object": "card",
  "title": "Competitor pricing — Linear vs Height",
  "description": null,
  "status": "review",
  "priority": "normal",
  "board": {
    "id": "b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61",
    "name": "Growth research"
  },
  "taskType": {
    "key": "competitor_summary",
    "name": "Competitor summary"
  },
  "inputs": {
    "urls": [
      "https://linear.app/pricing",
      "https://height.app/pricing"
    ],
    "focus": "pricing"
  },
  "labels": [
    "pricing"
  ],
  "assignee": null,
  "dueAt": null,
  "runCount": 1,
  "lastRunId": "e5a2c8f1-9b3d-4a7e-b6c4-1f8d2e7a9c35",
  "totalCostCents": 2.4,
  "createdAt": "2026-09-27T09:14:03.000Z",
  "updatedAt": "2026-09-27T09:14:03.000Z",
  "url": "https://dispatch.asrar.dev/app/asrar-studio/boards/b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61?card=7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40"
}
Runs & artifactsPOST/api/v1/cards/{cardId}/runs

Start a run

Queues a run of the card. It starts at once when a concurrency slot is free, otherwise it waits in Queued.
Scopesruns:write→ 202

Path parameters

  • cardIduuidrequired

    The card to run.

Body

  • instructionsstring

    Extra instruction for this run, like a comment sent to the agent.

  • modelTierstring

    advanced needs the Pro plan.

    standardadvanced

Good to know

  • Runs count toward the workspace’s monthly usage.
  • Side-effecting actions still wait for a person to approve them in Dispatch.
Request
curl -X POST "https://dispatch.asrar.software/api/v1/cards/$CARD_ID/runs" \
  -H "Authorization: Bearer $DISPATCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instructions": "Focus on annual plans and seat minimums."
  }'
Response
{
  "id": "e5a2c8f1-9b3d-4a7e-b6c4-1f8d2e7a9c35",
  "object": "run",
  "cardId": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
  "number": 1,
  "status": "queued",
  "trigger": "api",
  "modelTier": "standard",
  "liveStatus": "Queued",
  "queuedAt": "2026-09-27T09:15:10.000Z",
  "startedAt": null,
  "finishedAt": null,
  "durationMs": null,
  "usage": {
    "inputTokens": 0,
    "outputTokens": 0,
    "costCents": 0
  },
  "error": null,
  "createdAt": "2026-09-27T09:15:10.000Z",
  "url": "https://dispatch.asrar.dev/app/asrar-studio/boards/b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61?card=7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40&tab=run"
}
GET/api/v1/runs/{runId}

Retrieve a run

Status, live status line, timings, tokens and cost. Poll it, or subscribe to run.completed webhooks.
Scopesruns:read→ 200

Path parameters

  • runIduuidrequired

    The run id.

Request
curl "https://dispatch.asrar.software/api/v1/runs/$RUN_ID" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "id": "e5a2c8f1-9b3d-4a7e-b6c4-1f8d2e7a9c35",
  "object": "run",
  "cardId": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
  "number": 1,
  "status": "running",
  "trigger": "api",
  "modelTier": "standard",
  "liveStatus": "Reading linear.app/pricing…",
  "queuedAt": "2026-09-27T09:15:10.000Z",
  "startedAt": "2026-09-27T09:15:11.000Z",
  "finishedAt": null,
  "durationMs": null,
  "usage": {
    "inputTokens": 5210,
    "outputTokens": 640,
    "costCents": 1.9
  },
  "error": null,
  "createdAt": "2026-09-27T09:15:10.000Z",
  "url": "https://dispatch.asrar.dev/app/asrar-studio/boards/b3e7a1f4-2c9d-4e6b-8a1f-5d3c7e9b2a61?card=7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40&tab=run"
}
GET/api/v1/runs/{runId}/artifacts

List a run’s artifacts

What the run produced — documents, tables, JSON or email drafts — with citations.
Scopesartifacts:read→ 200

Path parameters

  • runIduuidrequired

    The run id.

Request
curl "https://dispatch.asrar.software/api/v1/runs/$RUN_ID/artifacts" \
  -H "Authorization: Bearer $DISPATCH_API_KEY"
Response
{
  "object": "list",
  "data": [
    {
      "id": "0c9e4b2a-7f1d-4e3a-9b8c-6d5a2f1e7c90",
      "object": "artifact",
      "cardId": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
      "runId": "e5a2c8f1-9b3d-4a7e-b6c4-1f8d2e7a9c35",
      "kind": "document",
      "title": "Linear vs Height — pricing",
      "version": 1,
      "isPartial": false,
      "content": {
        "markdown": "## Summary\nLinear’s Business plan costs $16 per user per month billed annually [[c1]]…"
      },
      "text": "Summary — Linear’s Business plan costs $16 per user per month billed annually…",
      "citations": [
        {
          "index": 1,
          "sourceType": "url",
          "url": "https://linear.app/pricing",
          "title": "Linear pricing",
          "excerpt": "Business — $16 per user/month, billed annually",
          "locator": {
            "anchor": "c1"
          }
        }
      ],
      "createdAt": "2026-09-27T09:16:02.000Z",
      "updatedAt": "2026-09-27T09:16:02.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
Webhooks

Webhook events

Add an endpoint in Tools & integrations → Webhooks and choose its events. Dispatch POSTs a JSON event to it as soon as something happens.
EventSent when
card.createdA card was added to a board (app, import, template, schedule or API).
run.startedAn agent picked a card up.
run.completedThe run finished and its artifact is ready for review.
run.failedThe run failed or stopped with a partial artifact.
approval.requestedThe agent wants to perform a side effect and waits for a person.
approval.decidedSomeone approved, edited or rejected a proposed action.

Every event has the same envelope: id (unique, use it to de-duplicate), type, createdAt, the workspace and a data object with the ids you need to fetch the full resource.

POST to your endpoint
{
  "id": "evt_6c1f0a9e2b7d4c3e8f5a1b9d0e2c4f7a",
  "type": "run.completed",
  "apiVersion": "2026-09-01",
  "createdAt": "2026-09-27T09:16:03.000Z",
  "workspace": {
    "id": "a1b2c3d4-…",
    "slug": "asrar-studio"
  },
  "data": {
    "runId": "e5a2c8f1-9b3d-4a7e-b6c4-1f8d2e7a9c35",
    "cardId": "7d1c9a52-4f0e-4b8e-9c3a-2f6d1e8b5a40",
    "number": 1,
    "status": "completed",
    "costCents": 2.4,
    "durationMs": 52000,
    "artifactIds": [
      "0c9e4b2a-7f1d-4e3a-9b8c-6d5a2f1e7c90"
    ]
  }
}

Verifying signatures

Each request is signed with the endpoint’s secret (whsec_…, shown once when you create or rotate it). The X-Dispatch-Signature header holds a timestamp and an HMAC-SHA256 of "<t>.<raw body>" in hex.
  1. 1Read the raw request body — before any JSON parsing — and the X-Dispatch-Signature header.
  2. 2Compute HMAC-SHA256 of ${t}.${rawBody} with your secret and compare it to each v1 value in constant time.
  3. 3Reject timestamps older than five minutes, so a captured request can’t be replayed.
  4. 4Answer 2xx within 10 seconds, then do the work. Anything else counts as a failed attempt.

Rotating a secret

During a rotation the header can carry several v1 values. Accept the request when any of them matches.
Verify on your server
import crypto from 'node:crypto';

// Express: app.post('/hooks/dispatch', express.raw({ type: 'application/json' }), …)
export function verifyDispatchSignature(rawBody, header, secret) {
  const parts = (header ?? '').split(',').map((p) => p.trim().split('='));
  const t = Number(parts.find(([k]) => k === 't')?.[1]);
  const signatures = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
  if (!t || !signatures.length) return false;
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // replay window: 5 min

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest();
  return signatures.some((sig) => {
    const given = Buffer.from(sig, 'hex');
    return given.length === expected.length && crypto.timingSafeEqual(given, expected);
  });
}

Retries & delivery log

A delivery that doesn’t get a 2xx is retried with exponential backoff, up to 5 attempts over about 42 min 30 s. Every attempt is logged with its response code and latency, and you can redeliver any event from the log.
  • Deliveries can arrive out of order and, rarely, more than once. Use the event id to de-duplicate.
  • Redirects are not followed. Point the endpoint at its final URL.
  • Dispatch only calls public addresses: URLs that resolve to localhost or a private network are refused, both when you save the endpoint and at delivery.
  • Pause an endpoint to stop deliveries without losing its secret or its log.

Attempts after a failure

  1. 1
    Immediatelyt = 0
  2. 2
    +30 st ≈ 30 s
  3. 3
    +2 mint ≈ 2 min 30 s
  4. 4
    +8 mint ≈ 10 min 30 s
  5. 5
    +32 min · last attemptt ≈ 42 min 30 s

After the last attempt the delivery is marked failed. Redeliver it from the log once your endpoint is back.

Reference

OpenAPI & versioning

The full description of this API is published as OpenAPI 3.1 at /api/v1/openapi.json — import it into Postman, Insomnia or a code generator.
  • This is version v1 (document 1.0.0). Breaking changes ship under a new path; v1 keeps working.
  • New fields and event types can be added at any time. Ignore the ones you don’t know.
curl
curl "https://dispatch.asrar.software/api/v1/openapi.json" -o dispatch-openapi.json