Mark

Mark API & MCP reference

One call stamps any content with a permanent, publicly verifiable record of who made it and when. You hash the content locally, so the bytes never leave your machine. Verification is free, forever, with no account.

API base: https://makemark.io · MCP: https://makemark.io/mcp

Quickstart

Hash your content, send the fingerprint, get a permanent verify URL back.

curl
# 1. hash the content locally (only the fingerprint is sent)
HASH=$(shasum -a 256 report.pdf | cut -d' ' -f1)

# 2. stamp it
curl -sS https://makemark.io/proof \
  -H "authorization: Bearer pk_your_key" \
  -H "content-type: application/json" \
  -d "{\"contentHash\":\"$HASH\",\"title\":\"Q3 report\",\"visibility\":\"public\"}"
# → { "proofId": "proof_…", "verifyUrl": "https://makemark.io/p/proof_…", "status": "SIGNED", … }

Getting a key

API keys are portable credentials for agents, scripts, and other apps. Humans can sign in at makemark.io/account, choose a file, and use their lifetime sample without creating or handling a key. An agent with no inbox calls POST /keys or the create_account MCP tool directly and gets a usable key back in the same response, no human step at all:

self-serve, no auth, agent-friendly
curl -sS -X POST https://makemark.io/keys \
  -H "content-type: application/json" \
  -d '{"label":"my-agent"}'
# → { "id": "key_…", "secret": "pk_…", "creatorId": "key_…", "plan": "free" }
# secret is shown exactly once, store it now

Self-serve keys are rate-limited (3 per network per day) and start on the free plan under a fresh, unattached identity. Send the secret as authorization: Bearer pk_… on every later call. Agents that would rather not hold a key at all can pay per stamp with x402 instead.

Pricing

Verification is always free. Every durable account gets one lifetime sample stamp. Prepaid packs never expire and come from the same published offer manifest used by checkout and the pricing page.

StampsUSDOffer id
5 $1.00 bundle_5
10 $1.50 bundle_10
50 $5.00 bundle_50
100 $8.00 bundle_100
500 $35.00 bundle_500
1,000 $65.00 bundle_1000
2,000 $120.00 bundle_2000

REST API

Method & pathAuthWhat it does
POST /keysnone (rate-limited)Self-serve key: mint a free-plan key with no human step. 3 per network per day.
POST /proofkey or x402Create a stamp for a content hash. Returns a signed receipt + verify URL.
POST /verifynoneGiven a content hash, returns every stamp for it, earliest-anchored first.
GET /search?q=noneSearch public stamps by title (or exact id / hash) across creators. Paginated via nextCursor; private stamps never appear.
GET /proof/:idnonePublic projection of one proof (status, times, anchors).
GET /proof/:id/bundlenoneSelf-contained JSON to verify the proof offline.
GET /proof/:id/c2panoneC2PA 2.4 manifest (media content types).
GET /proof/:id/badge.svgnoneEmbeddable status badge.
GET /proof/:id/qr.svgnoneScannable QR that opens the proof record.
GET /proofskeyList the caller's own stamps, newest first. ?cursor= pages past the first page; ?q= searches your whole history. Returns { proofs, nextCursor }.
POST /proof/:id/revokekey (owner)Forward-only revoke.
POST /identitieskeyStart verifying a domain or handle ("prove it's you"). Returns publish instructions.
POST /identities/:id/verifykeyCheck the published token; on success your stamps carry the verified identity.
GET /identitiesnoneThe transparent public registry of every verified publisher.
GET /identities/:value/proofsnoneA verified publisher's public stamps (their "everything I publish" feed).

Verification endpoints are unmetered and need no account.

verify any content, no auth
curl -sS https://makemark.io/verify \
  -H "content-type: application/json" \
  -d '{"contentHash":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}'
# → { "exact": true, "matchCount": 1, "conflict": null,
#     "proofs": [ { "publisher": { "type": "domain", "value": "example.com",
#                                  "tier": "verified" }, … } ] }

Reading a verify result: exact means these exact bytes are stamped; publisher is the verified identity (never trust a name that isn't in it: null means an unverified account); conflict means two or more different accounts stamped the same bytes, earliest anchored first. A no-match on a re-encoded or screenshotted copy is expected, not evidence of fakery.

MCP (for agents)

The proof service speaks the Model Context Protocol over Streamable HTTP, so any MCP-capable agent (Claude Code, Claude Desktop, Cursor, Windsurf) can stamp and verify content directly.

Claude Code
claude mcp add --transport http mark https://makemark.io/mcp
claude_desktop_config.json / Cursor / Windsurf
{
  "mcpServers": {
    "mark": { "url": "https://makemark.io/mcp" }
  }
}

Tools exposed: create_account, create_proof, verify_proof, list_proofs, revoke_proof. Then tell the agent: "Stamp everything you finish."

ChatGPT Actions

A ready-to-import OpenAPI 3.1 schema covers POST /keys, POST /proof, POST /verify, and GET /proof/:id. In a Custom GPT's configuration, under Actions, choose Import from URL and paste:

stable URL
https://makemark.io/.well-known/gpt-actions.json

Set the action's auth to API Key, Bearer, and paste a key from Getting a key above (or leave it unauthenticated to only expose POST /keys and POST /verify, which need no key).

Agents: stamp autonomously, end to end

The whole loop needs no human once a standing instruction is set:

0 accountNo key yet? create_account (MCP) or POST /keys. One call, usable immediately, no signup page.
1 hashSHA-256 the finished bytes locally. Content never leaves the machine.
2 stampcreate_proof (MCP) or POST /proof. On 402, sign the advertised x402 payment and retry.
3 attachDrop the receipt's verifyUrl, byline, badgeEmbed, or qrUrl into the published artifact.
4 identityOnce, ever: POST /identities for your human's domain. If you manage their DNS (e.g. the Cloudflare API), create the returned TXT record yourself, then POST /identities/:id/verify. Every future stamp carries the verified name.
5 recoverEvery error body carries next + docs. Follow next; no support ticket.

Paying with x402

Agents with no account will be able to pay $0.10 per stamp ($0.05 with an API key) in USDC on Base using the x402 protocol (EIP-3009, gasless). Call POST /proof with no key; a 402 comes back describing how to pay, your x402 client signs the authorization, and the retried call returns a receipt. Payment is final on-chain, so there are no chargebacks and no stored card. Until this lane is switched on, the API returns 503 payment_unavailable pointing you to a free key instead; see pricing for status.

Identity without an account

A keyless x402 stamp still has a stable identity: your paying wallet is your identity. The stamp is recorded under your Base address (the one that signed the payment), so re-stamping the same bytes is idempotent and version chains (previousProofId) work with zero setup, exactly like a keyed account. You don't have to send anything extra.

Two optional fields let you add a human "who" without signing up:

displayNameA human label (e.g. "Acme Newsroom bot") shown on public stamps as self-asserted, unverified. Never rendered as a verified identity, so it can't impersonate one.
claimEmailThe human's email. They get a one-time link to sign in (their free Mark account is created automatically, no password) and these wallet stamps move into that account, which they keep and reuse. Nothing is created until they click; the email is never shown publicly.

You may also pass your own creatorId (a did:web / did:key you control) to chain stamps across rotating wallets. Reserved prefixes (payer:, account:, tatastu:, key:) are rejected. A self-chosen creatorId can't be claimed by email (it proves no control), so claimEmail only applies to stamps recorded under your paying wallet. For a truly verified name, verify a domain or handle via the identity flow (needs a key).

Error responses that tell you the next step

Every auth or payment failure returns a small JSON body an agent can act on:

example 401 / 402 / 503 body
{
  "error": "payment_unavailable",
  "message": "Create a durable Mark account to claim one lifetime sample: POST /keys with no auth, use the create_account MCP tool, or sign in on the account page.",
  "next": "https://makemark.io/account",
  "pricing": "https://makemark.io/pricing",
  "docs": "https://makemark.io/docs"
}
StatuserrorMeaning
400invalid_content_hashcontentHash must be 64 hex chars (sha256).
401unauthorizedMissing or revoked API key. Sign in for a free one.
402quota_exhaustedMonthly included stamps used up and no credits. See pricing; resets on the 1st (UTC).
402payment_requiredx402 payment needed; the body describes how to pay.
429too_many_requestsRate limited. Retry after the given delay.
429rate_limitedPOST /keys only: 3 self-serve accounts per network per day. Wait, or sign in for a human account.
503payment_unavailablePay-per-stamp lane is off; use an API key.

Offline verification

Fetch the bundle and check the Ed25519 signature and the RFC 6962 Merkle inclusion path with zero network calls. A small standalone verifier ships in the MIT-licensed @tatastu/mark npm package as @tatastu/mark/verify/offline, so those checks can run without Mark. Keep the bundle and original file. Offline checks do not validate ledger state or anchor publication time; independently check the public anchor references for that. Arweave is the configured witness; Base is optional.

bundle
curl -sS https://makemark.io/proof/proof_xxx/bundle > bundle.json
# contains: receipt, signature, signing pubkey, merkle path + root, anchors

What a stamp proves (and what it does not)

A stamp asserts provenance from a signer at a time, not authorship truth. Two timestamps are always shown: the claimed time (when the service signed) and the proven time (when a daily batch root is independently anchored). Daily batches use Arweave, with Base available as an optional additional witness. It covers the exact bytes you hashed, not a live web page. That honesty is the point: it is evidence anyone can check, forever, without trusting Tatastu.