Quickstart

From an API key to a sealed, witness-cosigned, offline-verifiable verdict on a financial claim, in five steps. Every snippet on this page runs verbatim against the production API and was validated against it before publication.

Base URL https://api.artizar.ai verdict-v4 · policy-v7 · binding-v4 · lexicon-v7 Machine-readable spec: openapi.json Design-partner program: how to get a key

Before you start

You need an API key. Keys are issued by the Artizar founders under the design-partner program; the raw key is shown once at issuance and never retrievable again. Every request carries it:

Authorization: Bearer artz_<key-id-hex>_<secret>

Store it in your secret manager and export it as ARTIZAR_API_KEY for the snippets below. Any authentication failure (missing, malformed, revoked) returns the same generic 401. Rotate the key on receipt (step 4): the copy the operator saw at issuance is then dead.

Step 1: POST a claim

One financial-statement claim: "metric for period was value unit", about a filer identified by ticker or CIK.

curl -s https://api.artizar.ai/v1/verifications \
  -H "Authorization: Bearer $ARTIZAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "ticker": "AAPL",
        "metric": "revenue",
        "period": "FY2023",
        "claimed_value": "383285000000",
        "unit": "USD"
      }'

Step 2: read the sealed verdict

The 201 response is a sealed verdict: the deterministic outcome, the evidence chain to the primary source as filed, the version stamps that make it reproducible, and an Ed25519 seal binding all of it to a leaf of Artizar's public transparency log.

{
  "api_version": "v1",
  "sealed": {
    "verdict": {
      "outcome": "confirmed", "tier": "green",
      "subject_market": "edgar", "subject_issuer_id": "0000320193",
      "subject_cik": "0000320193",
      "metric": "revenue", "period": "FY2023",
      "claimed_value": "383285000000", "claimed_unit": "USD",
      "verified_value": "383285000000", "verified_unit": "USD",
      "refusal_reason": null,
      "rationale": "…cites the EDGAR URL + content hash…",
      "evidence": [ { "document_url_stable": "https://data.sec.gov/…", "content_hash": "…", "…": "…" } ],
      "context_notes": [
        { "code": "fiscal_frame", "text": "FY2023 is the filer's own fiscal label: the bound fact's period ends 2023-09-30, …", "origin": "deterministic" },
        { "code": "revenue_tag", "text": "Confirmed under us-gaap:RevenueFromContractWithCustomerExcludingAssessedTax — …", "origin": "deterministic" }
      ],
      "notes_version": "notes-v6",
      "verdict_schema_version": "verdict-v4", "binding_version": "binding-v4",
      "scoring_policy_version": "policy-v7", "lexicon_version": "lexicon-v7"
    },
    "record_id": "5b332ef9-…", "tenant_id": "your-tenant",
    "commitment": "…64-hex…", "commitment_version": "commit-v1",
    "leaf_index": 757, "entry_hash": "…64-hex…",
    "sealed_at": "…", "seal_version": "seal/v1",
    "seal_key_id": "2d7421f9…", "seal_signature": "…hex…"
  }
}

A verdict is never a binary "true." confirmed means the primary source (SEC EDGAR XBRL) states the claimed value within tolerance, with the filing URL, accession number, and content hash attached so you can check the source yourself. The five version stamps make a re-verification under the same stamps the same computation: reproducible, not merely asserted.

Context notes: the analyst-grade caveats, machine-readable

Where the record warrants it, the verdict carries context_notes: up to 8 structured {code, text, origin} caveats an agent can branch on or put straight into a spreadsheet cell, e.g. fiscal_frame (the period label is the filer's own fiscal frame, not a calendar period), instant_asof (the balance-sheet date a point-in-time value is as-of), or metric_definition_equity (stockholders' equity is parent-only; the including-NCI figure is a different fact). Every ebitda_calculated verdict carries derived_definition (the published formula) and calculated_vs_adjusted (this is the GAAP-component figure, not the company's non-GAAP Adjusted EBITDA, which can differ materially). Every free_cash_flow_calculated verdict carries the same two codes: its formula, with the minus sign, and the caveat that a company's own reported free cash flow may be defined differently. Every note is deterministic: derived by a closed, versioned rule table (notes_version), never authored by a model, and notes never influence the tier. The full vocabulary lives in the trust pack's verdict schema. Refusal verdicts get actionable notes too: an unmapped_metric refusal names the currently supported metric set.

Refusals are first-class

When Artizar cannot verify, it says so in a sealed verdict of its own: outcome="not_verifiable", tier="no_rating", and a machine-readable refusal_reason you can branch on: unresolved_subject (retry with a CIK), unmapped_metric, unsupported_value, unsupported_market, ambiguous_fiscal_label (the filer's own annual filings do not assign the requested fiscal period one label; refused, never guessed). Refusals are sealed, committed, retrievable, and charged like any verdict: they are provable output.

Safe retries. Send an Idempotency-Key header (a unique value per request, up to 255 visible ASCII characters; a UUID is ideal). A retry with the same key and the same body within 24 hours returns the original sealed verdict as a 200 with Idempotent-Replayed: true: it is not charged again and adds nothing to the public log. The same key with a different body is a 409 idempotency_key_reused. Without the header, submissions are deliberately not idempotent: resubmitting the same claim is a fresh verification, a fresh charge, and an unlinkably fresh public commitment. That unlinkability is a privacy feature. Nobody reading the public log can see what you verified or how often.

Step 3: fetch the record and proof, verify everything offline

Your record is retrievable immediately; its witnessed proof exists after the next cosigning of the public tree.

# The full committed record (verdict, evidence, blinding factor, commitment, log anchor):
curl -s https://api.artizar.ai/v1/verifications/<record_id> \
  -H "Authorization: Bearer $ARTIZAR_API_KEY"

# The witnessed inclusion proof (checkpoint + entry + Merkle path):
curl -s https://api.artizar.ai/v1/verifications/<record_id>/proof \
  -H "Authorization: Bearer $ARTIZAR_API_KEY"

# The stored sealed verdict (recover your POST-response artifact byte-identically):
curl -s https://api.artizar.ai/v1/verifications/<record_id>/sealed \
  -H "Authorization: Bearer $ARTIZAR_API_KEY"

Lost the 201 body? GET …/sealed serves the stored sealed verdict: the exact signed bytes, never re-derived, so a seal-key rotation can never vanish your history (verify it against the published key set for its seal_key_id). A record whose seal predates durable storage answers 404 seal_not_available honestly.

The proof route serves only witness-cosigned tree heads: until the next cosigning covers your leaf it returns 404 proof_not_available with a Retry-After header; wait that long, and the first 200 carries a witnessed checkpoint. The POST already told you the window: proof_expected_after_seconds, counted from sealed.sealed_at. Run the three checks below before archiving a proof regardless: they are your independence from Artizar, not a workaround. The sealed verdict deliberately bridges the wait: it already binds your verdict to the exact leaf (leaf_index/entry_hash) the proof will cover, so you can act on the verdict at once and archive the proof when it lands.

The three offline checks

Artizar's public keys are published at artizar.ai/.well-known/artizar-keys.json (the seal key, the log key, and the witness keys). With those and the responses above, your compliance code re-verifies everything with no call back to Artizar. The reference verifiers ship in the artizar client package included in your onboarding bundle; the checks are also fully specified for independent reimplementation: standard Ed25519, SHA-256, and RFC-6962-style Merkle verification, no Artizar SDK required:

from artizar.audit import AuditLogEntry, CosignedTreeHead
from artizar.audit.merkle import InclusionProof
from artizar.audit.monitor import verify_record_against_checkpoint
from artizar.seal import SealedVerdictRecord, verify_sealed_verdict
from artizar.types import CommittedVerificationRecord, compute_commitment

# 1. The seal: Artizar signed THIS verdict, bound to THIS record + commitment.
sealed = SealedVerdictRecord.model_validate(post_response["sealed"])
assert verify_sealed_verdict(sealed, seal_public_key_hex=ARTIZAR_SEAL_KEY).valid

# 2. The record: the public commitment is exactly the hash of what you hold.
record = CommittedVerificationRecord.model_validate(record_response["record"])
assert compute_commitment(record.record) == record.commitment == sealed.commitment

# 3. The tree: known witnesses attest your leaf is in the public append-only log.
entry = AuditLogEntry.model_validate(proof_response["entry"])
assert verify_record_against_checkpoint(
    entry.entry_hash,
    InclusionProof.model_validate(proof_response["inclusion_proof"]),
    CosignedTreeHead.model_validate(proof_response["checkpoint"]),
    log_public_key_hex=ARTIZAR_LOG_KEY,
    witness_public_keys=ARTIZAR_WITNESS_KEYS,
)
assert entry.entry_hash == sealed.entry_hash   # the seal's anchor IS the proven leaf

Hand the record + proof to your own auditor or regulator: whoever holds them can recompute the commitment and check the public tree without an Artizar account.

Step 4: rotate your key

Roll your credential yourself, with zero downtime, including the very first key you were handed:

# 1. Mint a successor (your current key stays live; deploy at your own pace):
curl -s -X POST https://api.artizar.ai/v1/keys/rotations \
  -H "Authorization: Bearer $ARTIZAR_API_KEY"
# → 201 { "key_id": "…", "raw_key": "artz_…" }   # shown once: store it now

# 2. When the new key is deployed, retire the old one WITH the new key:
curl -s -X POST https://api.artizar.ai/v1/keys/<old-key-id>/revocations \
  -H "Authorization: Bearer $ARTIZAR_NEW_KEY"
# → 201; the old key's next request is a 401

At most two keys may be active at once; a rotation while a previous one is unfinished is 409 rotation_blocked (revoke the retired key first). You can never revoke your last active key (409 last_active_key): rotate first, so you always hold a working credential. Every rotation is on the append-only record: the new key's issue event names the key that minted it. Rotation never resets your quota or rate limits; both follow your tenant, not the key.

Key hygiene. The raw key belongs in your secret manager or an environment variable, never in source control, a synced file, a chat message, or a shell command that echoes on error. If a key may have been exposed, rotate immediately: mint the successor, deploy it, revoke the exposed key. That is one curl each.

Step 5: plug in the MCP server

Give any MCP-compatible agent the API as tools. There are two ways in: the hosted endpoint, for agent platforms that cannot run a local process, and a local server on your own machine. Either way, authentication, quota, rate limits, and above all the deterministic tier are enforced server-side, and every result is the API's own JSON.

Hosted: https://api.artizar.ai/mcp

Point your agent's MCP client at the endpoint over Streamable HTTP and send your key as a bearer token, Authorization: Bearer artz_…, on every request. The endpoint is stateless (there is no session to keep), answers in JSON, and serves both protocol eras: the stateless 2026-07-28 revision and the handshake revisions back to 2024-11-05. Without a valid key, every request is a 401, before any tool is listed.

curl -s https://api.artizar.ai/mcp \
  -H "Authorization: Bearer $ARTIZAR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Four tools: verify_financial_claim (the Step 1 request body as arguments, plus an optional idempotency_key), get_verification, get_verification_proof, and list_supported_metrics (free: the node's own metric registry, with each metric's definition, period kind, and unit, stamped with its lexicon version). A result is exactly the JSON the matching REST route returns.

Retries and refusals. Give each claim an idempotency_key and reuse it when you retry: the retry returns the original sealed verdict without a second charge (the rule of the Idempotency-Key header, and the two share one namespace, so the same key and claim through either door is one verification). A refusal is a tool error whose structured content is the error body (code, retryable, retry_after_seconds, doc_url), so an agent can act on it without parsing prose. Results are quoted filing data, never instructions.

Local, over stdio

Give an agent host on your machine (Claude Code, Claude Desktop, Cursor, …) three tools over this API: verify a claim, fetch the record, fetch the proof. The server is a strict HTTP client running on your machine; it adds no policy, and the sealed verdict passes through byte-faithful.

# from the client wheel in your onboarding bundle:
pip install 'artizar[mcp] @ file:./artizar-<version>-py3-none-any.whl'
{
  "mcpServers": {
    "artizar-verify": {
      "command": "artizar-mcp",
      "env": {
        "ARTIZAR_API_URL": "https://api.artizar.ai",
        "ARTIZAR_API_KEY": "artz_…"
      }
    }
  }
}

Both variables are required; the server refuses to start without them, and the error names the variable and never echoes a value.

Watching your usage

curl -s https://api.artizar.ai/v1/usage \
  -H "Authorization: Bearer $ARTIZAR_API_KEY"
# → { "tenant_id": "…", "period": "2026-07", "limit": 1000, "used": 41, "remaining": 959, … }

Your own tenant only: the endpoint takes no tenant parameter. limit/remaining are null when no quota grant is active. Billing counts one metered charge per produced verdict (refusals included, since they consumed the same verification work and are equally provable); rejected requests (4xx) and outages are never charged. Usage is per calendar month, UTC.

Errors and limits

Every error is one JSON envelope: {"error": {"code", "message", "details", ...}}. code is stable; details lists the failing input locations and never the values you sent. The other fields appear only when they apply:

Statuserror.codeMeaningRetry?What to do
400errorthe request body could not be readnosend a complete, well-formed request
401unauthenticatedmissing/invalid/revoked key (one generic body)nocheck the key; rotate if needed
403forbiddenthe key lacks this capability (not reachable with a normal key)nocontact Artizar
404not_foundunknown record id, or not yoursnocheck the id
404proof_not_availableyour record is newer than the latest witnessed headyes, after retry_after_secondswait, then fetch the proof again; the POST's proof_expected_after_seconds gives the same window up front
404no_checkpointno witnessed tree head exists yetyes, after retry_after_secondswait, then retry
404seal_not_availablethe record predates durable seal storagenouse the record and its proof
405method_not_allowedwrong HTTP method for this pathnouse a method listed in the Allow header
409idempotency_key_reusedthis Idempotency-Key was already used with a different requestnouse a new key for a new request
409rotation_blockedtwo keys are already activenorevoke one, then rotate
409last_active_keyrevoking this key would leave the tenant with nonenorotate first, then revoke
409key_already_revokedthe key is already revokednonothing to do
413payload_too_largethe request body is larger than 1 MiBnosend a smaller body
422invalid_requestmalformed body / unknown field (locations listed, values never echoed)nofix the request; param and allowed_values say what to change
422unknown_metricmetric is not a supported name (the value is never echoed; not charged)nouse a name from allowed_values (exact, lowercase)
429rate_limitedper-IP or per-tenant rate limit: back offyes, after Retry-Afterhonor Retry-After; the RateLimit-* headers help you self-throttle
429quota_exhaustedmonthly quota spent (no Retry-After): buy morenocontact Artizar; resets at the calendar month (UTC)
500internal_erroran unexpected server error; quote the trace_id in the messagea GET: yes. The POST: only with its original Idempotency-Keyretry a GET; resend a POST only with the same key, which is charged at most once (without a key, a retry may be charged twice)
503verification_unavailableSEC EDGAR unreachable; you were not chargedyes, with backoffretry with exponential backoff; a retry cannot double-charge

Rate limits are layered: per-IP (availability) and per-tenant (follows your credential; key rotation or multiple egress IPs don't change it). Batch clients should honor Retry-After and pace themselves. Artizar's own benchmark client does exactly that against this same API.