Skip to content

API guide

JSON over HTTPS at https://api.quietdesk.dev/v1. The browsable, per-operation reference is generated from the OpenAPI spec — start at API operations.

Beta

The API is in open beta. This page and the spec are hand-maintained drafts; before GA they will be generated from the canonical Zod schemas. Shapes may gain fields; they will not change meaning.

Authentication

Bearer tokens, created in Settings → API keys. A token acts as you, with your role, in one organization.

bash
curl https://api.quietdesk.dev/v1/todos \
  -H "Authorization: Bearer qd_live_…"

Scopes: read, write, replay, config:write. MCP write tools require a write-scoped token.

Errors

Every error uses one envelope. user_message is safe to show; internals are never exposed.

json
{
  "code": "gated_move_requires_human",
  "http_status": 403,
  "user_message": "Moving this card to Done requires a human — agents can't approve work.",
  "request_id": "req_01JX9A2K"
}
StatusMeaning
400Malformed request
401Missing/invalid token
403Role or gate forbids it (incl. gated_move_requires_human)
404Not found, deleted, or outside your organization
409Conflict (e.g. idempotency key reused with a different body)
422Validation failed — field errors included
429Rate limited — honor Retry-After
5xxOur fault; retry with backoff, quote request_id

Pagination

Cursor-based. Pass limit (default 25, max 100); follow next_cursor until absent.

bash
curl "https://api.quietdesk.dev/v1/snippets?limit=50&cursor=eyJvZmZzZXQiOjUwfQ" \
  -H "Authorization: Bearer qd_live_…"

Idempotency

Send Idempotency-Key on any write. Same key + same body ⇒ the original result is replayed; same key + different body ⇒ 409. Keys live for 24 h and are stored transactionally with the write they guard.

A robust client, in 20 lines

js
async function qd(path, { method = 'GET', body, key } = {}) {
  for (let attempt = 0; attempt < 3; attempt++) {
    const res = await fetch(`https://api.quietdesk.dev/v1${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.QD_TOKEN}`,
        'Content-Type': 'application/json',
        ...(key ? { 'Idempotency-Key': key } : {}),
      },
      body: body ? JSON.stringify(body) : undefined,
    })
    if (res.status === 429 || res.status >= 500) {
      await new Promise(r => setTimeout(r, 400 * 2 ** attempt))
      continue
    }
    if (!res.ok) throw new Error((await res.json()).user_message)
    return res.status === 204 ? null : res.json()
  }
  throw new Error('quietdesk: retries exhausted')
}

// usage
const snip = await qd('/snippets', { method: 'POST', key: 'ik_001',
  body: { type: 'sql', name: 'Refund check', body: 'SELECT 1;' } })
console.log(snip.public_url)

See also: API conventions for the full cross-cutting rules table.

One vocabulary, every door.