Preview — not for production reliance
Documentation

Docs API reference

Claims

Domain claims, DNS challenges, evidence, appeals and jobs.

10 operations. See the API overview for authentication, errors, idempotency and caching rules that apply to all of them.

File a domain claim

POST/v1/claims

Authentication
Session (passkey-verified) or Service token
Operation ID
createClaim

Claims an exact domain on behalf of an organization and issues the first DNS challenge. Owner or domain administrator (session or service token). The domain must already be in the directory: an unknown name is 422 semantic_check_failed with field_errors[0].message = domain_not_in_directory; add it first with POST /v1/domains, which checks live DNS and a publicly trusted certificate for the exact name. An organization may have only one open claim per domain (409) and at most 10 claims per rolling hour (429).

The challenge value is shown once. Publish it as a TXT record at challenge.dns_name, then call POST /v1/claims/{id}/verify.

Parameters

NameInRequiredTypeDescription
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderNostringRequired when authenticating with a session cookie; not used with a service token.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: CreateClaimRequest

FieldTypeRequiredDescription
organization_idOrganizationIdYes
domainstringYesAn exact hostname. It is canonicalized; a name that is not a valid registrable-domain hostname is a field error carrying the reason.

Example

json
{
  "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
  "domain": "payments.example.com"
}

Responses

StatusDescriptionBody
201The claim and its first challenge.
Headers: Cache-Control: no-store
application/json ClaimCreated
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
422semantic_check_failed. The request is well formed but a policy or proof check failed (for example an issuer-control proof did not verify, a challenge expired, or the organization is not yet verified). field_errors[].message is often a stable reason code.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

201 example

json
{
  "claim": {
    "id": "clm_4c8e2g6k0m3q7s1w5y9a2d6f8h",
    "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
    "domain": "payments.example.com",
    "state": "pending_domain",
    "resume_state": null,
    "version": 1,
    "claimant_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
    "created_at": "2026-10-05T11:50:00.000Z",
    "updated_at": "2026-10-05T11:50:00.000Z",
    "expires_at": null,
    "domain_verified_at": null,
    "identity_verified_at": null,
    "authority_verified_at": null,
    "authority_expires_at": null,
    "permitted_actions": []
  },
  "challenge": {
    "id": "chl_3m6p9s2v5y8b1e4h7k0n3q6t9w",
    "dns_name": "_workload-directory.payments.example.com",
    "record_type": "TXT",
    "expires_at": "2026-10-06T11:50:00.000Z",
    "max_attempts": 5,
    "value": "art-verify=Vd2Qm9Xk4Lw7Zp0Nb3Ts6Ra1Ey8Hc5Jf"
  },
  "instructions": "Publish a TXT record at _workload-directory.payments.example.com whose value is exactly the challenge value, then POST /v1/claims/{id}/verify. The value is shown only once. Both Cloudflare and Google public DNS must see it. Request a new challenge if it expires (24 h) or after 5 failed checks."
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

422 example

json
{
  "code": "semantic_check_failed",
  "message": "Issuer-control proof failed (endpoint_http_404). 4 attempt(s) left on this challenge.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "method",
      "message": "endpoint_http_404"
    }
  ]
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Get a claim

GET/v1/claims/{id}

Authentication
Session (passkey-verified) or Service token
Operation ID
getClaim

Any member. Returns the claim, the three check results, the five most recent challenges (status only, never secrets), the organization-visible evidence (never storage keys, reviewer identity or reviewer notes), failed checks, suggested next_actions and, when a 90-day domain recheck is open, domain_recheck.

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.

Responses

StatusDescriptionBody
200The claim detail.application/json ClaimDetail
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "claim": {
    "id": "clm_4c8e2g6k0m3q7s1w5y9a2d6f8h",
    "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
    "domain": "payments.example.com",
    "state": "pending_identity",
    "resume_state": null,
    "version": 3,
    "claimant_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
    "created_at": "2026-10-05T11:50:00.000Z",
    "updated_at": "2026-10-05T12:02:00.000Z",
    "expires_at": null,
    "domain_verified_at": "2026-10-05T12:02:00.000Z",
    "identity_verified_at": null,
    "authority_verified_at": null,
    "authority_expires_at": null,
    "permitted_actions": []
  },
  "information_request": null,
  "checks": {
    "domain_control": "passed",
    "organization_identity": "pending",
    "representative_authority": "pending"
  },
  "challenges": [
    {
      "id": "chl_3m6p9s2v5y8b1e4h7k0n3q6t9w",
      "purpose": "claim",
      "principal_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
      "dns_name": "_workload-directory.payments.example.com",
      "expires_at": "2026-10-06T11:50:00.000Z",
      "consumed_at": "2026-10-05T12:02:00.000Z",
      "attempts": 1,
      "status": "consumed",
      "last_failure": null,
      "last_checked_at": "2026-10-05T12:02:00.000Z",
      "created_at": "2026-10-05T11:50:00.000Z"
    }
  ],
  "evidence": [],
  "failed_checks": [],
  "next_actions": [
    "submit_organization_identity_evidence"
  ],
  "domain_recheck": null
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Replace the DNS challenge

POST/v1/claims/{id}/challenges

Authentication
Session (passkey-verified) or Service token
Operation ID
createClaimChallenge

Issues a new challenge (lost value, expiry, attempts exhausted) and supersedes the live one. Owner or domain administrator. Only while domain control is the pending step (409 otherwise). At most 5 challenges per claim, ever (429).

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderNostringRequired when authenticating with a session cookie; not used with a service token.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
201The new challenge. The value is shown once.
Headers: Cache-Control: no-store
application/json ChallengeCreated
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

201 example

json
{
  "challenge": {
    "id": "chl_5a8c1f4h7k0m3p6s9v2x5z8b1d",
    "dns_name": "_workload-directory.payments.example.com",
    "record_type": "TXT",
    "expires_at": "2026-10-06T12:10:00.000Z",
    "max_attempts": 5,
    "value": "art-verify=Ne5Tw1Zk8Lb4Vq7Xc0Ma3Rs6Hy9Dp2Jf"
  },
  "instructions": "Publish a TXT record at _workload-directory.payments.example.com whose value is exactly the challenge value, then POST /v1/claims/{id}/verify. The value is shown only once. Both Cloudflare and Google public DNS must see it. Request a new challenge if it expires (24 h) or after 5 failed checks."
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Check the DNS challenge

POST/v1/claims/{id}/verify

Authentication
Session (passkey-verified) or Service token
Operation ID
verifyClaimDomain

Enqueues a DNS check of your own live challenge and returns 202 with a job to poll at GET /v1/jobs/{id}. For a verified or suspended claim this verifies the 90-day recheck challenge instead. While a check of the same challenge attempt is queued or running, the same job is returned. No live challenge for the caller's account, an expired one or an exhausted one is 422 semantic_check_failed.

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderNostringRequired when authenticating with a session cookie; not used with a service token.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
202The check was queued.application/json JobAccepted
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
422semantic_check_failed. The request is well formed but a policy or proof check failed (for example an issuer-control proof did not verify, a challenge expired, or the organization is not yet verified). field_errors[].message is often a stable reason code.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

202 example

json
{
  "job_id": "job_2k5n8r1v4y7b0e3h6m9q2t5x8a",
  "status": "queued",
  "challenge_id": "chl_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

422 example

json
{
  "code": "semantic_check_failed",
  "message": "Issuer-control proof failed (endpoint_http_404). 4 attempt(s) left on this challenge.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "method",
      "message": "endpoint_http_404"
    }
  ]
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Reveal the 90-day recheck challenge

POST/v1/claims/{id}/recheck-challenge

Authentication
Session (passkey-verified)
Operation ID
revealRecheckChallenge

Human only; owner or domain administrator. For a verified claim whose recheck is due, or a claim suspended because the recheck lapsed. Each reveal rotates the token (the old value stops working) and is bound to the revealing account. Reveals are capped per person and per claim in a 14-day window (429). Publish the value, then call POST /v1/claims/{id}/verify.

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Responses

StatusDescriptionBody
201The recheck challenge. The value is shown once.
Headers: Cache-Control: no-store
application/json ChallengeCreated
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
422semantic_check_failed. The request is well formed but a policy or proof check failed (for example an issuer-control proof did not verify, a challenge expired, or the organization is not yet verified). field_errors[].message is often a stable reason code.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

201 example

json
{
  "challenge": {
    "id": "chl_7c0e3g6j9m2p5s8v1x4z7b0d3f",
    "purpose": "recheck",
    "dns_name": "_workload-directory.payments.example.com",
    "record_type": "TXT",
    "expires_at": "2027-01-02T12:00:00.000Z",
    "max_attempts": 5,
    "attempts": 0,
    "value": "art-verify=Rb8Xp3Zk0Nw6Lq2Vc5Ta9Hs1Ey4Dm7Jf"
  },
  "instructions": "Publish a TXT record at _workload-directory.payments.example.com whose value is exactly the challenge value, then POST /v1/claims/{id}/verify. The value is shown only once. Both Cloudflare and Google public DNS must see it. Request a new challenge if it expires (24 h) or after 5 failed checks."
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

422 example

json
{
  "code": "semantic_check_failed",
  "message": "Issuer-control proof failed (endpoint_http_404). 4 attempt(s) left on this challenge.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "method",
      "message": "endpoint_http_404"
    }
  ]
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Appeal a rejected claim

POST/v1/claims/{id}/appeal

Authentication
Session with step-up (passkey within 300 s)
Operation ID
appealClaim

Owner, human, step-up. Moves a rejected claim to disputed for a reviewer to uphold or reject; the reason goes to the audit trail for the reviewer and is never public. At most 3 appeals per claim (429). expected_version, when sent, must equal the claim's current version. The organization must not hold another open claim on the domain (409).

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: AppealRequest

FieldTypeRequiredDescription
reasonstringYesPlain text, no control characters. Kept in the audit trail for the reviewer; never public.
1 to 2000 characters
expected_versionintegerNoIf sent, must equal the claim's current version.

Example

json
{
  "reason": "The registry record was updated on 2026-10-01 and now matches our legal name.",
  "expected_version": 5
}

Responses

StatusDescriptionBody
200The claim, now disputed.application/json ClaimEnvelope
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "claim": {
    "id": "clm_4c8e2g6k0m3q7s1w5y9a2d6f8h",
    "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
    "domain": "payments.example.com",
    "state": "disputed",
    "resume_state": "pending_identity",
    "version": 6,
    "claimant_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
    "created_at": "2026-10-05T11:50:00.000Z",
    "updated_at": "2026-10-06T09:00:00.000Z",
    "expires_at": null,
    "domain_verified_at": "2026-10-05T12:02:00.000Z",
    "identity_verified_at": null,
    "authority_verified_at": null,
    "authority_expires_at": null,
    "permitted_actions": []
  }
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Withdraw a claim

POST/v1/claims/{id}/withdraw

Authentication
Session with step-up (passkey within 300 s)
Operation ID
withdrawClaim

Owner, human, step-up. Terminal. Withdrawing a verified claim stops its issuer bindings from resolving, in the same commit. Live challenges are cancelled and open conflicts are resolved. expected_version must equal the claim's current version (409 otherwise).

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: ExpectedVersionRequest

FieldTypeRequiredDescription
expected_versionintegerYesThe version you last read; a stale value is 409.
minimum 1

Example

json
{
  "expected_version": 7
}

Responses

StatusDescriptionBody
200The claim, now withdrawn.application/json ClaimEnvelope
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "claim": {
    "id": "clm_4c8e2g6k0m3q7s1w5y9a2d6f8h",
    "organization_id": "org_7k3m9q2x8d5f1h4j6n0p2r4t8v",
    "domain": "payments.example.com",
    "state": "withdrawn",
    "resume_state": null,
    "version": 8,
    "claimant_id": "prn_6d9g2j5m8q1t4w7z0c3f6h9k2n",
    "created_at": "2026-10-05T11:50:00.000Z",
    "updated_at": "2026-10-07T10:00:00.000Z",
    "expires_at": null,
    "domain_verified_at": "2026-10-05T12:02:00.000Z",
    "identity_verified_at": "2026-10-05T14:00:00.000Z",
    "authority_verified_at": "2026-10-05T14:30:00.000Z",
    "authority_expires_at": "2027-10-05T14:30:00.000Z",
    "permitted_actions": [
      "authorize_issuer"
    ]
  }
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Begin an evidence submission

POST/v1/claims/{id}/evidence

Authentication
Session (passkey-verified)
Operation ID
submitClaimEvidence

Human only; owner or domain administrator. Records a submitted evidence row (only a reviewer can pass evidence) and returns a one-time upload instruction for each declared file. At most 20 submissions per claim, 5 files each.

organization_identity is accepted while identity review is the pending step; representative_authority while identity or authority is. details is a kind-specific structured object; unknown detail keys are rejected.

kind details.type other detail fields
organization_identity ca_certificate certificate_reference (required, 500)
registry_record registry_number (required, 100), registry_url (required, https, 500), registry_name (200)
other at least one file; any of the above optional
representative_authority independent_contact contact_name (required, 200), contact_channel (required, 200), contact_role (200)
delegated_authority_letter at least one file (the letter)
company_email company_email (required, 254)
other at least one file

Upload each file with PUT /v1/evidence-uploads within 1 hour.

Parameters

NameInRequiredTypeDescription
idpathYesClaimIdClaim id.
Idempotency-KeyheaderYesstringUnique key for this logical mutation (1 to 255 characters of A-Z a-z 0-9 . _ : -). Replays within 24 hours return the original outcome.
1 to 255 characters; pattern ^[A-Za-z0-9._:-]+$
example 7f3c1b52-9d44-4a0e-8f21-0a6e5b9d3c77
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/json, required: SubmitEvidenceRequest

FieldTypeRequiredDescription
kindone of "organization_identity", "representative_authority"Yes
descriptionstringYesPlain text for the reviewer.
1 to 2000 characters
detailsEvidenceDetailsYes
filesarray of DeclaredFileNoat most 5 items

Example

json
{
  "kind": "organization_identity",
  "description": "Delaware registry extract for the legal entity.",
  "details": {
    "type": "registry_record",
    "registry_name": "Delaware Division of Corporations",
    "registry_number": "5849201",
    "registry_url": "https://icis.corp.delaware.gov/"
  },
  "files": [
    {
      "name": "registry-extract.pdf",
      "content_type": "application/pdf",
      "size": 182044
    }
  ]
}

Responses

StatusDescriptionBody
201The evidence record and its upload instructions. Tokens are shown once.
Headers: Cache-Control: no-store
application/json EvidenceSubmitted
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
429rate_limited. A per-client or per-record limit was reached; retry later.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

201 example

json
{
  "evidence": {
    "id": "evi_3m6p9s2v5y8b1e4h7k0n3q6t9w",
    "claim_id": "clm_4c8e2g6k0m3q7s1w5y9a2d6f8h",
    "kind": "organization_identity",
    "result": "submitted",
    "created_at": "2026-10-05T12:20:00.000Z",
    "files": [
      {
        "id": "evf_5a8c1f4h7k0m3p6s9v2x5z8b1d",
        "name": "registry-extract.pdf",
        "content_type": "application/pdf",
        "size": 182044,
        "status": "awaiting_upload",
        "upload_expires_at": "2026-10-05T13:20:00.000Z",
        "upload": {
          "method": "PUT",
          "url": "https://svid.io/v1/evidence-uploads",
          "headers": {
            "x-art-upload-token": "Zr4Qm8Xk1Lw5Vp9Nb2Ts6Ra0Ey3Hc7Jf4Dg1Ma8Pq5"
          }
        }
      }
    ]
  }
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

429 example

json
{
  "code": "rate_limited",
  "message": "Too many requests.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Upload one evidence file

PUT/v1/evidence-uploads

Authentication
Session (passkey-verified)
Operation ID
uploadEvidenceFile

Streams one declared file into private storage. The body is the raw file, not JSON; there is no Idempotency-Key (the single-use token makes a retry after success a 409). Authorization is BOTH the unguessable one-time token in x-art-upload-token (43 characters of base64url; a header so it never appears in URLs or logs) AND the uploader's own passkey session (with CSRF and Origin). Human only.

Content-Type must equal the content_type declared at submission and Content-Length must equal the declared size (415 / 400 or 413, code invalid_request). The bytes are re-validated against the declared type; a bad file burns the token (submit again), a transient storage failure hands it back. An unknown, someone else's, expired or revoked token is 404; an already-used one is 409.

Parameters

NameInRequiredTypeDescription
x-art-upload-tokenheaderYesstringThe one-time upload token from the evidence submission.
pattern ^[A-Za-z0-9_-]{43}$
x-art-csrfheaderYesstringThe csrf_token from GET /auth/session. Required on every session-authenticated mutation.
example Kk3jVq0bYw2p0mT1aZc9sD8eF4gH6iJ7nL5oP2rQ1uA

Request body

application/pdf, required: string (binary)

The file bytes.

image/png, required: string (binary)

The file bytes.

image/jpeg, required: string (binary)

The file bytes.

Responses

StatusDescriptionBody
200Stored.application/json EvidenceFileStored
400invalid_request. Malformed JSON, an invalid or unknown field (see field_errors), a missing or malformed Idempotency-Key, or a bad query. Also used with status 413 (body over 64 KiB) and 415 (wrong Content-Type).application/json Error
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
403forbidden. Authenticated but not allowed: a role that does not permit the action, a service token on a human-only operation, a missing or wrong CSRF token, or a request Origin that is not allowed.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
409conflict. The record changed since you read it (stale expected_version), the action is not valid in the record's current state, the Idempotency-Key was reused with a different request, or the same request is still in flight.application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "id": "evf_5a8c1f4h7k0m3p6s9v2x5z8b1d",
  "evidence_id": "evi_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "status": "stored",
  "size": 182044,
  "sha256": "9f2c4ab07d1e5b83c6a0f4d92e7b1a35c8d0e6f4a2b9713d5c8e0f1a2b3c4d5e"
}

400 example

json
{
  "code": "invalid_request",
  "message": "Invalid organization.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w",
  "field_errors": [
    {
      "field": "legal_name",
      "message": "must be 1-200 characters"
    }
  ]
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

403 example

json
{
  "code": "forbidden",
  "message": "Your role does not permit this action.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

409 example

json
{
  "code": "conflict",
  "message": "The record changed since you read it; reload and retry with the current version.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

Poll an asynchronous job

GET/v1/jobs/{id}

Authentication
Session (passkey-verified) or Service token
Operation ID
getJob

The job's owner only (the principal that started it); anything else is 404.

Parameters

NameInRequiredTypeDescription
idpathYesstring
example job_2k5n8r1v4y7b0e3h6m9q2t5x8a

Responses

StatusDescriptionBody
200Job state.application/json Job
401unauthenticated (no or invalid credentials, or the session has not completed the passkey ceremony) or step_up_required (the passkey assertion is too old; also returned to service tokens, which can never step up). Both are 401; tell them apart by code.application/json Error
404not_found. No such resource, or one the caller may not see (the two are indistinguishable by design).application/json Error
503unavailable (temporarily unavailable; also a feature that is not configured in this deployment) or provider_unavailable (an identity provider is not configured). Both are 503. During maintenance (for example a database recovery) every mutating request answers unavailable "The directory is under maintenance; try again later." Reads keep working.application/json Error

200 example

json
{
  "id": "job_2k5n8r1v4y7b0e3h6m9q2t5x8a",
  "kind": "verify_dns",
  "state": "done",
  "attempts": 1,
  "result": {
    "outcome": "verified",
    "claim_state": "pending_identity",
    "evidence_id": "evi_6d9g2j5m8q1t4w7z0c3f6h9k2n"
  },
  "created_at": "2026-10-05T12:02:00.000Z",
  "updated_at": "2026-10-05T12:02:05.000Z"
}

401 unauthenticated

json
{
  "code": "unauthenticated",
  "message": "Sign in required.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

401 step_up_required

json
{
  "code": "step_up_required",
  "message": "Recent passkey verification required for this action.",
  "request_id": "req_5a8c1f4h7k0m3p6s9v2x5z8b1d"
}

404 example

json
{
  "code": "not_found",
  "message": "No such resource.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}

503 example

json
{
  "code": "unavailable",
  "message": "Temporarily unavailable.",
  "request_id": "req_3m6p9s2v5y8b1e4h7k0n3q6t9w"
}