Buying results

Receipts

Every purchase ends in an immutable receipt: what was asked, what ran, what it cost, what was refunded.

Every live execute request ends in one receipt. It records the tool, the fingerprint of what you sent, and for every item the provider that answered, the outcome of the published check, the version of that check, the fingerprint of the result, what was charged and what went back. A receipt never changes. A refund from an upheld dispute is added on top of it, so the receipt still shows what happened at the time. This page has every field, where to read receipts (API, SDKs, MCP, CSV, dashboard) and a full example.

Arettic is pre-launch. Keys go to design partners first; everyone else joins the waitlist. The @arettic/sdk and @arettic/mcp packages (npm) and the arettic package (PyPI) are published at launch. Test keys (sk_test_…) write no receipts: receipt_id is absent from a test response, and GET /v1/receipts lists live purchases only. See Test mode.

When a receipt is written

A receipt is written once every item of a request has settled, that is, once each one is charged or released. For one input or a batch of up to 25, that is before the execute answer comes back, and the answer carries receipt_id. For a batch of more than 25, the job gets its receipt when it finishes, and GET /v1/jobs/{job_id} then carries receipt_id. A retry with the same idempotency_key never writes a second receipt: the replayed answer carries the first receipt_id. See Idempotency.

If the process dies between the last item settling and the receipt, a safety net writes the receipt a few minutes later, so a replay never hangs on a purchase that has no receipt. An item caught mid-call by the same crash is released with the reason interrupted and never charged.

The receipt, field by field

Money is always { "credits": "7", "usd": "0.007" }: credits as a whole number in a string (1 credit is $0.001) and the same amount in dollars with three decimals.

Top-level receipt fields
FieldTypeMeaning
receipt_idUUIDThe receipt's id. It is the receipt_id on the execute answer.
created_atISO 8601 timestampWhen the receipt was written: after the last item settled.
tool_idstring or nullThe tool the request ran on, by its slug (for example hunter-verify-email). When a paused tool was replaced before the call (fallback: true), this is the replacement; the execute answer named the original in substituted_for.
job_idUUID or nullThe job, when the request was a batch of more than 25. Otherwise null.
idempotency_keystring or nullThe idempotency_key you sent, or null if you sent none.
request_hash64 hex characters or nullSHA-256 of the whole request: the tool and every input. See Hashes.
check_versionstring or nullThe version of the pass rules the items were checked with. Currently v1.
chargedmoneyWhat was captured across all items. This never changes, even after a dispute is upheld.
refundedmoneyWhat went back: the held credits of items that were not charged, plus every refund from an upheld dispute on this receipt. This is the one figure that grows after the receipt is written.
summaryobjectitems, passed, partial and failed: one count per input, after any fallback.
itemsarrayOne entry per item attempt, in index order. A fallback attempt is a second entry with the same index. See below.

Items

Each entry in items[] is one attempt at one input: one execution, one hold, one provider call, one check, one settlement. reason, fallback_of and dispute are present only when they apply.

Item fields
FieldTypeMeaning
indexinteger from 0The position of the input in inputs (0 for a single input). The original attempt and its fallback share it.
execution_idUUIDThe attempt's id. It is the execution_id on a single-input answer, and what a dispute can name instead of receipt_id + item_index.
tool_idstringThe tool that ran this attempt, by slug. A fallback attempt names its own tool.
providerstringThe provider behind that tool, by slug (for example hunter).
outcomestringThe settlement: captured, released or expired. See the table below.
reasonstring, optionalWhy the item was not a pass: a reason code such as status:unknown, no_result, provider_error, timeout, interrupted, cancelled or job_time_limit. Absent on a pass.
chargedmoneyWhat was captured for this attempt.
refundedmoneyThe rest of the hold, given back: price minus charged. A dispute refund is not in here; it is in dispute.refunded and in the receipt's top-level refunded.
pricing_modestringper_success (the default: a pass is charged, a fail is not) or per_call (every checked call is charged, pass or fail; provider errors stay free). See Per-call pricing.
input_hash64 hex charactersSHA-256 of this input. See Hashes.
result_hash64 hex characters or nullSHA-256 of the provider's answer as it was checked. null when there was no answer (a provider error, a timeout, an interrupted or expired item).
check_versionstring or nullThe pass-rule version this attempt was checked with (v1).
fallback_ofUUID, optionalOn a fallback attempt: the execution_id of the first attempt. See Fallback.
settled_atISO 8601 timestamp or nullWhen the attempt was captured or released.
disputeobject, optionalWhen this attempt has been disputed: dispute_id, status (open, upheld or rejected) and refunded (money; 0 until upheld).
Item outcomes
outcomeMoneyWhen
capturedcharged is above 0The result passed its check, or was a partial pass (web search, charged pro rata), or the org is on per-call pricing for the tool and the call was checked.
releasedcharged is 0, refunded is the full priceThe check failed, the provider errored or timed out, the checker itself threw (check_error), a job cancelled the item before it ran (cancelled), or a crash caught it mid-call (interrupted).
expiredcharged is 0, refunded is the full priceA job hit its 2-hour limit before this item ran (job_time_limit). Only on receipts with a job_id.

The receipt's charged is the sum of the items' charged; its refunded is the sum of the items' refunded plus the sum of every dispute.refunded. The CSV export adds up the same way, row by row.

Hashes

The three hashes let you prove, later, what you asked and what you got, without Arettic keeping the data. Inputs and results themselves are stored encrypted for 7 days (longer while a dispute on them is open); the hashes stay on the receipt.

Every hash is SHA-256, as 64 lowercase hex characters, of the value's canonical JSON: object keys sorted at every level, arrays in their order, no whitespace, strings and numbers encoded as JSON. The values hashed are:

To check a hash yourself, canonicalise and hash the value you hold. In Node this is the same code the API runs:

TypeScript (Node)
import { createHash } from "node:crypto";

// Canonical JSON: keys sorted at every level, no whitespace.
function canonical(v: unknown): string {
  if (Array.isArray(v)) return `[${v.map(canonical).join(",")}]`;
  if (v && typeof v === "object")
    return `{${Object.keys(v as Record<string, unknown>)
      .sort()
      .map((k) => `${JSON.stringify(k)}:${canonical((v as Record<string, unknown>)[k])}`)
      .join(",")}}`;
  return JSON.stringify(v);
}
const sha256 = (s: string) => createHash("sha256").update(s).digest("hex");

// input_hash of the input you sent; result_hash of the result a passed item returned.
console.log(sha256(canonical({ email: "[email protected]" })));
console.log(sha256(canonical(bought.result)));
Python
import hashlib
import json

# The same canonical form for values that came off the wire as JSON:
# strings, whole numbers, booleans, null, lists and objects.
def canonical(value):
    return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)

def sha256(text):
    return hashlib.sha256(text.encode("utf-8")).hexdigest()

print(sha256(canonical({"email": "[email protected]"})))  # compare with input_hash
print(sha256(canonical(bought["result"])))  # compare with result_hash

Read one receipt

GET /v1/receipts/{receipt_id} with a live agent key. Any agent of the org can read any of the org's receipts, not only its own. An id that is not a UUID, or that belongs to another org, answers 404 not_found; the message never says which.

curl
curl https://api.arettic.com/v1/receipts/7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d \
  -H "Authorization: Bearer $ARETTIC_API_KEY"
TypeScript
import { Arettic } from "@arettic/sdk";

const arettic = new Arettic({ apiKey: process.env.ARETTIC_API_KEY });

const receipt = await arettic.receipt("7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d");
console.log(receipt.charged.usd, receipt.refunded.usd, receipt.summary);
for (const item of receipt.items) {
  console.log(item.index, item.provider, item.outcome, item.reason ?? "", item.charged.credits);
}
Python
from arettic import Arettic

client = Arettic()  # reads ARETTIC_API_KEY (and ARETTIC_API_URL, if set)

receipt = client.receipt("7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d")
print(receipt["charged"]["usd"], receipt["refunded"]["usd"], receipt["summary"])
for item in receipt["items"]:
    print(item["index"], item["provider"], item["outcome"], item.get("reason", ""), item["charged"]["credits"])

Over MCP the get_receipt tool takes receipt_id and returns the same JSON as text content; a refusal comes back as a tool error whose text is the error envelope. Use it with a live key: test calls write no receipts, so there is nothing of a test agent's own to read. Client configs are on MCP server.

MCP tool call
{
  "name": "get_receipt",
  "arguments": { "receipt_id": "7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d" }
}

A full receipt for a batch of three verify-email inputs: two passed, one failed on status:unknown, and the last item was later disputed and upheld. Note that charged stays at 14 while refunded is the 7 not charged plus the 7 refunded by the dispute. The ids and hashes are made up.

Response (example)
{
  "receipt_id": "7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
  "created_at": "2026-09-29T10:14:07.512Z",
  "tool_id": "hunter-verify-email",
  "job_id": null,
  "idempotency_key": "verify-batch-1",
  "request_hash": "9f2c1e8a7b6d5c4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1a0f",
  "check_version": "v1",
  "charged": { "credits": "14", "usd": "0.014" },
  "refunded": { "credits": "14", "usd": "0.014" },
  "summary": { "items": 3, "passed": 2, "partial": 0, "failed": 1 },
  "items": [
    {
      "index": 0,
      "execution_id": "c02576cf-b3ce-4b0f-a30c-6e8aad4328ce",
      "tool_id": "hunter-verify-email",
      "provider": "hunter",
      "outcome": "captured",
      "charged": { "credits": "7", "usd": "0.007" },
      "refunded": { "credits": "0", "usd": "0.000" },
      "pricing_mode": "per_success",
      "input_hash": "1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
      "result_hash": "d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3",
      "check_version": "v1",
      "settled_at": "2026-09-29T10:14:06.201Z"
    },
    {
      "index": 1,
      "execution_id": "9b1f0d2e-4c6a-4e8b-9f3d-2a7c5e1b8d40",
      "tool_id": "hunter-verify-email",
      "provider": "hunter",
      "outcome": "released",
      "reason": "status:unknown",
      "charged": { "credits": "0", "usd": "0.000" },
      "refunded": { "credits": "7", "usd": "0.007" },
      "pricing_mode": "per_success",
      "input_hash": "2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a",
      "result_hash": "e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4",
      "check_version": "v1",
      "settled_at": "2026-09-29T10:14:06.744Z"
    },
    {
      "index": 2,
      "execution_id": "5e8c1a7b-2d3f-4a6e-8b9c-1f2e3d4c5b6a",
      "tool_id": "hunter-verify-email",
      "provider": "hunter",
      "outcome": "captured",
      "charged": { "credits": "7", "usd": "0.007" },
      "refunded": { "credits": "0", "usd": "0.000" },
      "pricing_mode": "per_success",
      "input_hash": "3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b",
      "result_hash": "f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5",
      "check_version": "v1",
      "settled_at": "2026-09-29T10:14:07.030Z",
      "dispute": {
        "dispute_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "status": "upheld",
        "refunded": { "credits": "7", "usd": "0.007" }
      }
    }
  ]
}

With fallback: true, an input that failed on the first tool and passed on the next appears twice, with the same index. Only the passing attempt is charged:

Response (example): the items of a fallback receipt
"items": [
  {
    "index": 0,
    "execution_id": "0d2e7f31-8a4b-4c9d-b1e6-5f7a9c3d2e10",
    "tool_id": "hunter-verify-email",
    "provider": "hunter",
    "outcome": "released",
    "reason": "status:unknown",
    "charged": { "credits": "0", "usd": "0.000" },
    "refunded": { "credits": "7", "usd": "0.007" },
    "pricing_mode": "per_success",
    "input_hash": "1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
    "result_hash": "e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4",
    "check_version": "v1",
    "settled_at": "2026-09-29T11:02:15.118Z"
  },
  {
    "index": 0,
    "execution_id": "3f9c2c1e-6d7a-4b8f-a2e1-9c0b5d4e3f21",
    "tool_id": "zerobounce-verify-email",
    "provider": "zerobounce",
    "outcome": "captured",
    "charged": { "credits": "6", "usd": "0.006" },
    "refunded": { "credits": "0", "usd": "0.000" },
    "pricing_mode": "per_success",
    "input_hash": "1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
    "result_hash": "0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b",
    "check_version": "v1",
    "fallback_of": "0d2e7f31-8a4b-4c9d-b1e6-5f7a9c3d2e10",
    "settled_at": "2026-09-29T11:02:16.402Z"
  }
]

List receipts

GET /v1/receipts with a live agent key lists the org's receipts, newest first, one line per receipt. It is the same list the dashboard's receipt explorer shows. Filters go in the query string; all are optional.

Query parameters of GET /v1/receipts
ParameterFormatMeaning
fromYYYY-MM-DDFirst day to include (UTC). Without it, 30 days before to (or before now).
toYYYY-MM-DDLast day to include, inclusive (UTC). Without it, now.
agent_idagent UUIDOnly this agent's receipts. A value that is not a UUID is ignored.
tool_idtool slugOnly receipts for this tool, for example hunter-verify-email.
beforethe next value of the previous pageCursor: receipts older than this timestamp. Anything else is invalid_input.
limit1 to 200Receipts per page. The default is 50; values outside the range are clamped.

The range may cover at most 366 days, and from must be before to; otherwise the answer is 400 invalid_input. Each page carries next: pass it as before for the next page, and stop when it is null.

curl
curl "https://api.arettic.com/v1/receipts?from=2026-09-01&to=2026-09-29&tool_id=hunter-verify-email&limit=50" \
  -H "Authorization: Bearer $ARETTIC_API_KEY"
TypeScript
import { Arettic } from "@arettic/sdk";

const arettic = new Arettic({ apiKey: process.env.ARETTIC_API_KEY });

// Every receipt for one tool in September, page by page.
const query = { from: "2026-09-01", to: "2026-09-30", toolId: "hunter-verify-email", limit: 200 };
let page = await arettic.receipts(query);
while (true) {
  for (const r of page.receipts) console.log(r.created_at, r.agent_name, r.charged.usd, r.net.usd);
  if (!page.next) break;
  page = await arettic.receipts({ ...query, before: page.next });
}
Python
from arettic import Arettic

client = Arettic()

# `from` is a Python keyword, so the SDK takes from_ and sends it as from.
query = {"from_": "2026-09-01", "to": "2026-09-30", "tool_id": "hunter-verify-email", "limit": 200}
page = client.receipts(**query)
while True:
    for r in page["receipts"]:
        print(r["created_at"], r["agent_name"], r["charged"]["usd"], r["net"]["usd"])
    if not page["next"]:
        break
    page = client.receipts(**query, before=page["next"])
Response (example)
{
  "receipts": [
    {
      "receipt_id": "7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
      "created_at": "2026-09-29T10:14:07.512Z",
      "agent_id": "4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a",
      "agent_name": "Research, EU",
      "tool_id": "hunter-verify-email",
      "job_id": null,
      "summary": { "items": 3, "passed": 2, "partial": 0, "failed": 1 },
      "charged": { "credits": "14", "usd": "0.014" },
      "dispute_refunded": { "credits": "7", "usd": "0.007" },
      "net": { "credits": "7", "usd": "0.007" },
      "open_disputes": 0
    }
  ],
  "next": null
}
Fields of a receipt in the list
FieldMeaning
receipt_id, created_at, tool_id, job_id, summary, chargedAs on the receipt.
agent_id, agent_nameThe agent that bought.
dispute_refundedMoney refunded by upheld disputes on this receipt.
netcharged minus dispute_refunded: what the receipt cost in the end. Credits released at settlement are not part of either.
open_disputesDisputes on this receipt not yet decided.
nextThe cursor for the next page, or null on the last one.

The org endpoints

The same reads exist under /v1/orgs/{orgId}/… for people and for org keys (ok_…, read scope is enough), with the member role or above. They take the same filters and return the same shapes. The CSV export exists only here.

Receipt endpoints
EndpointKeyReturns
GET /v1/receipts/{id}agent keyOne receipt.
GET /v1/receiptsagent keyThe org's receipts, newest first, with filters and paging.
GET /v1/orgs/{orgId}/receipts/{id}session or org keyOne receipt.
GET /v1/orgs/{orgId}/receiptssession or org keyThe same list, same filters.
GET /v1/orgs/{orgId}/exports/receipts.csvsession or org keyEvery item attempt in the range as CSV; filters from and to. See CSV export.
curl
curl "https://api.arettic.com/v1/orgs/$ARETTIC_ORG_ID/receipts?limit=200" \
  -H "Authorization: Bearer $ARETTIC_ORG_KEY"

curl https://api.arettic.com/v1/orgs/$ARETTIC_ORG_ID/receipts/7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d \
  -H "Authorization: Bearer $ARETTIC_ORG_KEY"

curl "https://api.arettic.com/v1/orgs/$ARETTIC_ORG_ID/exports/receipts.csv?from=2026-09-01&to=2026-09-30" \
  -H "Authorization: Bearer $ARETTIC_ORG_KEY" \
  -o arettic-receipts.csv
TypeScript
import { AretticOrg } from "@arettic/sdk";

const org = new AretticOrg({ apiKey: process.env.ARETTIC_ORG_KEY, orgId: "your-org-id" });

const page = await org.receipts.list({ from: "2026-09-01", to: "2026-09-30", limit: 200 });
const receipt = await org.receipts.get("7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d");
const csv = await org.receipts.exportCsv({ from: "2026-09-01", to: "2026-09-30" }); // a string
Python
import os

from arettic import AretticOrg

org = AretticOrg(os.environ["ARETTIC_ORG_KEY"], "your-org-id")

page = org.receipts.list(from_="2026-09-01", to="2026-09-30", limit=200)
receipt = org.receipts.get("7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d")
csv = org.receipts.export_csv(from_="2026-09-01", to="2026-09-30")  # a string

CSV export

GET /v1/orgs/{orgId}/exports/receipts.csv returns one row per item attempt (a fallback is its own row), oldest receipt first, for the same from and to range as the list (default the last 30 days, at most 366), up to 100,000 rows. The body is text/csv; charset=utf-8 with CRLF line endings, sent as an attachment named arettic-receipts.csv. Cells with commas, quotes or line breaks are quoted; a cell that starts with =, +, -, @ or a tab gets a leading ' so a spreadsheet never runs it as a formula. Per receipt, the charged_credits rows add up to the receipt's charged, and the net_credits rows to the list's net.

CSV columns, in order
ColumnMeaning
receipt_idThe receipt.
receipt_created_atWhen the receipt was written, ISO 8601.
agent_idThe agent that bought.
agent_nameIts name.
tool_idThe tool that ran this attempt, by slug.
providerIts provider, by slug.
item_indexThe input's position; the original and its fallback share it.
execution_idThe attempt's id.
attemptoriginal or fallback.
outcomecaptured, released or expired.
reasonThe reason code, or empty on a pass.
price_creditsThe price held for the attempt.
charged_creditsWhat was captured.
charged_usdThe same in dollars, three decimals.
not_charged_creditsprice_credits minus charged_credits: the hold given back.
dispute_statusopen, upheld, rejected, or empty when never disputed.
dispute_refunded_creditsCredits refunded by an upheld dispute, else 0.
net_creditscharged_credits minus dispute_refunded_credits.
net_usdThe same in dollars.
input_hashSHA-256 of the input.
result_hashSHA-256 of the result, or empty.
check_versionThe pass-rule version (v1).
settled_atWhen the attempt settled, ISO 8601.
Response (example)
receipt_id,receipt_created_at,agent_id,agent_name,tool_id,provider,item_index,execution_id,attempt,outcome,reason,price_credits,charged_credits,charged_usd,not_charged_credits,dispute_status,dispute_refunded_credits,net_credits,net_usd,input_hash,result_hash,check_version,settled_at
7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d,2026-09-29T10:14:07.512Z,4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a,"Research, EU",hunter-verify-email,hunter,0,c02576cf-b3ce-4b0f-a30c-6e8aad4328ce,original,captured,,7,7,0.007,0,,0,7,0.007,1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809,d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3,v1,2026-09-29T10:14:06.201Z
7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d,2026-09-29T10:14:07.512Z,4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a,"Research, EU",hunter-verify-email,hunter,1,9b1f0d2e-4c6a-4e8b-9f3d-2a7c5e1b8d40,original,released,status:unknown,7,0,0.000,7,,0,0,0.000,2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a,e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4,v1,2026-09-29T10:14:06.744Z
7c6b5a4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d,2026-09-29T10:14:07.512Z,4d3c2b1a-0f9e-4d8c-b7a6-5f4e3d2c1b0a,"Research, EU",hunter-verify-email,hunter,2,5e8c1a7b-2d3f-4a6e-8b9c-1f2e3d4c5b6a,original,captured,,7,7,0.007,0,upheld,7,0,0.000,3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b,f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5,v1,2026-09-29T10:14:07.030Z

The dashboard

Signed-in members see the same data at https://arettic.com/app/receipts. The explorer lists receipts newest first with the time (UTC), agent, tool, how many items were charged out of how many, what was charged and the net after dispute refunds, with a pill for open disputes. It filters by date, agent and tool, shows the last 30 days by default, and pages with an "Older receipts" link. "Download CSV" gives the export above for the same date range. Opening a receipt shows every item with its provider, outcome, reason and charge, and a "Dispute" form on any charged item for 7 days after it settled. Filing and following disputes is on Disputes.

Immutability and disputes

A receipt and its items are written once, after settlement, and never updated: charged, every item's outcome, charged, refunded, the hashes and check_version stay as they were. Two things are layered on top when a dispute is filed and decided:

A charged item can be disputed for 7 days after it settled; Arettic decides within 48 hours against the stored copy of the result, and a dispute not decided in time is upheld automatically. An upheld dispute returns the item's charge to the buckets it came from (trial or paid). The receipt's charged is not reduced, so a receipt read a year later still says what was paid on the day.

JSON Schema

The receipt's JSON Schema (draft 2020-12) is published at https://api.arettic.com/schemas/receipt.json and listed with the other schemas at https://arettic.com/schemas. Its version is bumped only when the shape changes in a way a validator would notice. Fields marked optional above (reason, fallback_of, dispute) are absent, not null, when they do not apply.

Errors

A refusal comes back as { "error": { "code", "message", "doc_url", "retryable" } }; the SDKs raise it as AretticApiError with the same fields. The codes you can get from the receipt endpoints:

Error codes on the receipt endpoints
codeHTTPWhen
not_found404The receipt id is not a UUID, or no receipt with that id belongs to your org. On the org endpoints, also when the person or key has no membership in that org.
invalid_input400from or to is not YYYY-MM-DD, from is not before to, the range is over 366 days, or before is not a valid cursor.
unauthenticated401No key, a revoked key, or a disabled agent. A test key can read receipts but sees none of its own, since test calls write none.
forbidden403An org key used on another org's /v1/orgs/{orgId}/… path.
rate_limited429Too many requests from this agent. Wait the Retry-After seconds and send it again; see Rate limits.

Where next

Disputes to challenge a charged item. Execute for the answer a purchase returns and every reason code. Batches and jobs for receipts that come from a job. Org API for everything an org key can read next to receipts. Error codes for the full registry.

Updated 2026-09-29 · This page as Markdown · JSON