API reference

Sellat API v2

Everything the web app does, from your own system: register a file’s SHA-256 fingerprint, anchor it on Polygon and Bitcoin, request the FNMT qualified timestamp, download the certificate and the evidence package, and keep the original in custody. REST and JSON, with an API key.

Base URL https://sellat.app/api/v2

Introduction

The API works with the file’s fingerprint, not the file: you compute the SHA-256 on your side and send only those 64 characters. The original never leaves your machine unless you choose to put it in custody.

Each proof goes into a Merkle batch anchored on Polygon, and the same batch is attested on Bitcoin. If you ask for it, the fingerprint also gets the qualified timestamp of FNMT-RCM, a qualified trust service provider on the EU trusted list.

Every route hangs off the base URL and speaks JSON. Proofs you create through the API and on the web belong to the same account and show up in the same dashboard.

It is meant to be called from a server: it sends no CORS headers, so a browser cannot call it from another site, and your key must never be in code that reaches a browser.

Authentication

Every request carries your key in the Authorization: Bearer sellat_... header. Keys are created and revoked from your dashboard; an account can hold up to 5 active keys, one per integration.

Without a key, or with a revoked one, the API answers 401 unauthorized with a WWW-Authenticate: Bearer header. The only public route is the portable proof (/proof/{id}.json).

Request
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer sellat_3f9c..."

Quick start

Compute the fingerprint, create the proof with the qualified seal and keep the id you get back. Within minutes it becomes anchored and all its URLs work.

Without "qualified": true the proof is just as verifiable (Polygon and Bitcoin) and spends no seal. You can request the seal later with POST /proofs/{id}/qualified.

Terminal
# Your key, from the dashboard
export SELLAT_API_TOKEN=sellat_...

# The fingerprint: the file stays on your machine
HASH=$(sha256sum contract.pdf | cut -d' ' -f1)

# The proof, with the FNMT qualified seal

curl https://sellat.app/api/v2/proofs \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"hash\":\"$HASH\",\"name\":\"contract.pdf\",\"qualified\":true}"

Proof states

The state field says what Sellat knows at each moment: it never reports a transaction as confirmed before it has checked it.

The Bitcoin attestation (bitcoin) runs separately: pending until OpenTimestamps includes it in a block, then confirmed with its height. The qualified seal (qualified) does not depend on the state: it is issued straight away.

StateWhat it means
queuedReceived. Waiting to join a batch, about two minutes at most.
batchedIn a Merkle batch, with its path fixed. Waiting for the Polygon anchor.
anchoredAnchored: the transaction is in a Polygon block. proof.json is available.
confirmedThe block has 12 confirmations.

Errors

An error is always JSON with error.code (stable, for your code) and error.message (for people). Some add fields, such as seals_remaining and buy_url on a 402. The portable proof, being public, answers {"error": "..."}.

If the gateway cuts a request before it reaches the API (too many requests from one IP, or a body over 25 MB), that 429 or 413 does not use this format.

CodeHTTPWhat happened
invalid_request400A field is missing or not in the expected format. The message says which.
unauthorized401No key, a malformed key or a revoked one.
payment_required402No qualified seals left. Carries seals_remaining and buy_url.
key_without_account403The key is not attached to an account (old keys).
not_found404The proof does not exist or belongs to another account.
conflict409Another account registered these bytes first, or the original is not stored yet.
hash_mismatch409The file you upload does not have the proof’s fingerprint.
file_too_large413The file exceeds your plan’s per-file limit (max_file_bytes).
rate_limited429Too many requests per minute. Honour Retry-After.
quota_exceeded429Daily proof limit reached. Retry-After runs to 00:00 UTC.
download_quota_exceeded429Daily limit of original downloads reached.
integrity_failed500The stored original no longer matches its fingerprint, so it is not served.
unavailable502 · 503An internal service is not answering. Retry later.
seal_unavailable503The timestamping authority is not available right now.
custody_unavailable503Custody is not available right now.
custody_quota_exceeded507No custody space left on your account.
Response
{
  "error": {
    "code": "payment_required",
    "message": "No qualified seals left on this account.",
    "seals_remaining": 0,
    "buy_url": "https://sellat.app/precios"
  }
}

Limits

Each account can create a number of API proofs per day, across all its keys. Proofs made on the web do not count, and neither does sending bytes you already own again. The day resets at 00:00 UTC.

At the limit, POST /proofs answers 429 quota_exceeded with a quota object and Retry-After. Each route also has a per-minute brake (for example 60 creations and 10 seals a minute) that answers 429 rate_limited, and the gateway accepts up to 120 requests a minute per IP. A request body can be 25 MB at most.

Qualified seals have no daily limit: each one spends a seal from your balance. Check what you have left at any time with GET /account, and if you need more volume, write to [email protected].

Free accountPro account
API proofs per day105,000
Active keys55
Original in custody, per file10 MB50 MB
Total custody50 MB5 GB
Original downloads per day1050
Response
HTTP/1.1 429 Too Many Requests
Retry-After: 41231

{
  "error": {
    "code": "quota_exceeded",
    "message": "Daily limit reached: 10 proofs per account per UTC day across all its keys.",
    "quota": {
      "limit": 10,
      "used": 10,
      "resets_in_seconds": 41231
    }
  }
}

Idempotency

Send an Idempotency-Key header (up to 200 characters) with POST /proofs and you can retry safely: if you already used it with the same API key, you get the original proof back with 200 and "created": false, whatever the new body says, and no seal is issued.

Without the header, sending bytes your account already owns also returns the existing proof (200), without changing its name or metadata. But if another account registered those bytes first, every request creates a new proof with limitations: if you might retry, always use Idempotency-Key.

Seals are never duplicated either: a proof has one seal, and asking again returns the same one without spending another.

Verify without Sellat

A Sellat proof does not need Sellat to be checked. With the file and its proof.json, the open-source verifier recomputes the fingerprint and the Merkle path and reads the anchor on the public blockchain.

The qualified seal is checked with OpenSSL, using the file, the .tsr and the authority’s certificate, which comes inside the evidence package. -partial_chain is needed because the trusted list publishes the authority’s own certificate, not a CA’s.

Terminal
# 1. The proof: file + proof.json + the public blockchain
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
npx sellat-verify contract.pdf proof.json

# 2. The qualified seal: file + .tsr + the authority's certificate
#    (autoridad-sellado.pem comes inside evidence.zip)
openssl ts -verify -in seal.tsr -data contract.pdf \
  -CAfile autoridad-sellado.pem -partial_chain

Proofs

Create a proof

POST /proofs

Registers a file’s SHA-256 fingerprint and returns the proof. With "qualified": true it also issues the FNMT qualified seal in the same call, spending one seal from the account.

Parameters

hash string · body required
The file’s SHA-256 fingerprint: 64 hexadecimal characters.
name string · body optional
What the dashboard and the certificate call it. Only the file name is kept, not the path. Defaults to the first 12 characters of the hash.
metadata object · body optional
A JSON object of yours, up to 4 KB. Stored with the proof, never returned or published.
size integer · body optional
File size in bytes. Informational.
mime_type string · body optional
The file’s type, used when serving the original if you keep it in custody. Defaults to application/octet-stream.
qualified boolean · body optional
true also issues the FNMT qualified seal.
Idempotency-Key string · header optional
Retry without duplicating (see Idempotency).

Responses

201
Proof created.
200
It already existed: these bytes are already your account’s, or you repeated an Idempotency-Key. Carries "created": false.
400
invalid_request: a field is missing or invalid.
401
unauthorized.
402
payment_required: you asked for the seal and have none left. Nothing is written.
409
conflict: you asked for the seal on bytes another account registered first.
429
quota_exceeded (daily limit) or rate_limited (per minute).
503
seal_unavailable or unavailable. If the proof was created, the error carries its proof_id.

If the authority is slow to answer, the proof is still created and qualified comes back as {"state": "pending"}: ask again with POST /proofs/{id}/qualified, at no cost.

If another account registered those bytes first, the proof is created and anchored but has no certificate of its own: the certificate URLs are null and the response carries "limitations": ["hash_registered_by_another_account"].

Request
curl https://sellat.app/api/v2/proofs \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: contract-2026-0142" \
  -d '{
    "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
    "name": "contract.pdf",
    "qualified": true,
    "metadata": { "ref": "case-2026-0142" }
  }'
Response
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "created": true,
  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
  "algorithm": "SHA-256",
  "name": "contract.pdf",
  "state": "queued",
  "received_at": "2026-09-30T09:12:03.418Z",
  "anchors": [],
  "bitcoin": {
    "state": "pending",
    "block_height": null
  },
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "original": null,
  "urls": {
    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
  }
}

Retrieve a proof

GET /proofs/{id}

Returns the proof with its state, its anchors, the Bitcoin attestation, the seal if it has one, the original if it is in custody, and all its URLs.

Parameters

id string · path required
The proof’s id.

Responses

200
The proof.
401
unauthorized.
404
not_found: it does not exist or belongs to another account.
Request
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
  "algorithm": "SHA-256",
  "name": "contract.pdf",
  "state": "anchored",
  "received_at": "2026-09-30T09:12:03.418Z",
  "anchors": [
    {
      "network": "Polygon Mainnet",
      "chain_id": 137,
      "state": "anchored",
      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
      "block_number": 94653624,
      "block_timestamp": "2026-09-30T09:14:36.000Z",
      "explorer_url": "https://polygonscan.com/tx/0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a"
    }
  ],
  "bitcoin": {
    "state": "pending",
    "block_height": null
  },
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "original": null,
  "urls": {
    "self": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
    "proof_json": "https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json",
    "certificate": "https://sellat.app/certificate/3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b",
    "certificate_pdf": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf",
    "evidence_zip": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip"
  }
}

List proofs

GET /proofs

Your account’s proofs, newest first: those created through the API and those created on the web. Each item in data is a full proof, like GET /proofs/{id}.

For the next page, pass the next_cursor the previous one returned. When it is null, there are no more.

Parameters

limit integer · query optional
Proofs per page, 1 to 100. Defaults to 25.
cursor string · query optional
The next_cursor of the previous page.

Responses

200
data and next_cursor.
400
invalid_request: invalid limit or cursor.
401
unauthorized.
Request
curl "https://sellat.app/api/v2/proofs?limit=25" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "data": [
    {
      "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
      "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f",
      "name": "contract.pdf",
      "state": "confirmed",
      "received_at": "2026-09-30T09:12:03.418Z"
    }
  ],
  "next_cursor": "1843"
}

Qualified seal (eIDAS)

Request the qualified seal

POST /proofs/{id}/qualified

Issues an RFC 3161 timestamp from FNMT-RCM over the file’s fingerprint. Under Article 41(2) of the eIDAS Regulation, a qualified electronic timestamp enjoys the presumption of the accuracy of the date and time it indicates and of the integrity of the data.

It spends one seal from the account: the welcome seal or one from your packs. The API never charges a card or opens a payment; packs are bought on the pricing page.

Parameters

id string · path required
The proof’s id.

Responses

200
Seal issued, or it already had one ("created": false, nothing spent). Carries seals_remaining.
202
The seal is paid for but the authority did not answer. Call again (Retry-After: 30): it completes at no cost.
401
unauthorized.
402
payment_required: no seals left. Carries seals_remaining and buy_url.
404
not_found.
409
conflict: another account registered these bytes first.
503
seal_unavailable.

A proof has one seal, forever. If issuance fails for good, the seal goes back to your balance.

Request
curl -X POST https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "created": true,
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  },
  "seals_remaining": 4
}

Retrieve the seal

GET /proofs/{id}/qualified

Returns only the proof’s seal, or null if it has none yet. It issues nothing and spends nothing.

Parameters

id string · path required
The proof’s id.

Responses

200
proof_id and qualified.
401
unauthorized.
404
not_found.
Request
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "qualified": {
    "state": "issued",
    "authority": "FNMT-RCM",
    "time": "2026-09-30T09:12:05.000Z",
    "serial_number": "175755C77B4239226AA0428FE1C107C1",
    "policy_oid": "0.4.0.2023.1.1",
    "tsr_url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr"
  }
}

Download the token (.tsr)

GET /proofs/{id}/qualified.tsr

The seal exactly as the authority returned it: an RFC 3161 TimeStampResp in DER. It is the file an expert or a court can validate without Sellat.

Parameters

id string · path required
The proof’s id.

Responses

200
The .tsr (application/timestamp-reply).
401
unauthorized.
404
not_found: the proof does not exist or has no seal yet.
Request
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o seal.tsr
Response (file)
HTTP/1.1 200 OK
Content-Type: application/timestamp-reply
Content-Disposition: attachment; filename="sellat-6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e-qualified-timestamp.tsr"

<RFC 3161 TimeStampResp, DER>

Certificate and evidence

PDF certificate

GET /proofs/{id}/certificate.pdf

The proof’s certificate, for people: the fingerprint, the date, the anchors and the qualified seal if there is one, with instructions to verify it.

Parameters

id string · path required
The proof’s id.
lang string · query optional
Certificate language: es, en, de or fr. Any other value gives en.

Responses

200
The PDF.
401
unauthorized.
404
not_found.
409
conflict: another account registered these bytes first and the proof has no certificate of its own.
Request
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/certificate.pdf?lang=es" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o certificate.pdf
Response (file)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="certificado-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.pdf"

<PDF>

Evidence package

GET /proofs/{id}/evidence.zip

A ZIP with what it takes to defend the proof without Sellat: the certificate, proof.json, the seal and the authority’s certificate if there is one, the instructions and, if it is in custody, the original.

Parameters

id string · path required
The proof’s id.
lang string · query optional
Language of the package’s texts: es, en, de or fr.

Responses

200
The ZIP.
401
unauthorized.
404
not_found.
409
conflict: another account registered these bytes first.

Including the original spends one of the day’s downloads. If none are left, the package comes without it and the README says so.

Request
curl "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/evidence.zip?lang=es" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o evidence.zip
Response (file)
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="sellat-evidencia-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.zip"

<ZIP>

Custody of the original

Deposit the original

PUT /proofs/{id}/original

Stores the file next to its proof, on servers in the European Union. Sellat recomputes the fingerprint of what it receives and refuses it if it does not match the proof.

Send the raw bytes with the file’s Content-Type, or multipart/form-data under the file field. POST works the same as PUT.

Parameters

id string · path required
The proof’s id.
file binary · body required
The file. Up to 10 MB on the free account and 50 MB on the Pro account, and 25 MB per request at most.

Responses

201
Stored. Returns the proof with original.
200
It was already stored.
400
invalid_request: the body is empty or unreadable.
401
unauthorized.
404
not_found.
409
hash_mismatch (not the proof’s file) or conflict (another account’s bytes).
413
file_too_large: over your plan’s per-file limit. Carries max_file_bytes.
502
unavailable: storage did not answer.
503
custody_unavailable.
507
custody_quota_exceeded: no space left on your account. Carries used_bytes and max_account_bytes.

Custody is optional: the proof is just as valid without the original. Keeping it adds preservation; it does not condition the proof.

Request
curl -X PUT https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary @contract.pdf
Response
{
  "id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "state": "anchored",
  "original": {
    "stored": true,
    "file_name": "contract.pdf",
    "size_bytes": 184320,
    "mime_type": "application/pdf",
    "stored_at": "2026-09-30T09:15:10.000Z",
    "url": "https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original"
  }
}

Download the original

GET /proofs/{id}/original

Returns the file in custody. Sellat recomputes its fingerprint on the way out and refuses to serve it if it no longer matches. Each download counts towards your plan’s daily limit; the X-Sellat-Custody-Downloads-Remaining header says how many are left.

Parameters

id string · path required
The proof’s id.

Responses

200
The file, with its type and name.
401
unauthorized.
404
No original in custody.
409
conflict: the original is not stored yet.
429
download_quota_exceeded: daily download limit. Resets at 00:00 UTC.
500
integrity_failed: what is stored no longer matches the fingerprint.
502
unavailable.
Request
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o contract.pdf
Response (file)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="contract.pdf"
X-Sellat-Custody-Downloads-Remaining: 9

<the original bytes>

End custody

DELETE /proofs/{id}/original

Deletes the original in custody. The proof does not change: it stays anchored, sealed and verifiable.

Parameters

id string · path required
The proof’s id.

Responses

200
"original": null and "proof_unaffected": true. "purged": false means physical deletion is queued.
401
unauthorized.
404
No original in custody.
Request
curl -X DELETE https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "original": null,
  "purged": true,
  "proof_unaffected": true
}

Account

Your account

GET /account

What the calling key can do: the plan, the seals you have left (welcome and packs), custody space and downloads, today’s proofs and their limit, and the active keys. Check it before a large batch.

The connector block holds the limits for connected shops (WooCommerce, coming soon).

Responses

200
The account.
401
unauthorized.
Request
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Response
{
  "plan": "free",
  "plan_name": "Free",
  "seals": {
    "available": true,
    "remaining": 1,
    "welcome_remaining": 1,
    "pack_remaining": 0,
    "price_eur": 6,
    "buy_url": "https://sellat.app/precios"
  },
  "custody": {
    "available": true,
    "used_bytes": 0,
    "max_account_bytes": 52428800,
    "max_file_bytes": 10485760,
    "downloads_used_today": 0,
    "max_downloads_per_day": 10
  },
  "proofs": {
    "daily_limit": 10,
    "used_today": 3,
    "resets_in_seconds": 41231
  },
  "connector": {
    "mode_available": "daily",
    "eidas_available": false,
    "custody_available": false,
    "operations": {
      "daily_limit": 2000,
      "resets_in_seconds": 41231
    }
  },
  "keys": {
    "active": 1
  },
  "urls": {
    "dashboard": "https://sellat.app/dashboard",
    "plans": "https://sellat.app/precios#pricing-business",
    "seals": "https://sellat.app/precios#pricing-seal-title",
    "docs": "https://sellat.app/developers/docs",
    "connect": "https://sellat.app/connect/woocommerce"
  }
}

Portable proof

Portable proof (proof.json)

GET /proof/{id}.json No key

The document that lets anyone verify the proof without Sellat, in the public sellat-proof/2 format: the fingerprint, the leaf, the Merkle path, the root, the Polygon anchor and the attestations (Bitcoin and the qualified seal). It is public on purpose: whoever holds it can check the proof without an account.

Parameters

id string · path required
The proof’s id.

Responses

200
The proof.json.
202
Not anchored yet: {"ready": false, "state": "..."} with Retry-After: 60.
400
The id is not a UUID.
404
It does not exist.
Request
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
Response
{
  "schema": "sellat-proof/2",
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "content": {
    "algorithm": "SHA-256",
    "hash": "9f3c2a5e0b7d1c4f8a6e2d9b3c7f1a5e8d2c6b0f4a9e3d7c1b5f8a2e6d0c4b9f"
  },
  "leaf": {
    "formula": "SHA-256('sellat-leaf:v2:' + proof_id + ':' + content.hash)",
    "value": "31668b4d846680cd920a1cb5be200ab0562909e8778223087c1738746a1cbb26"
  },
  "merkle": {
    "index": 1,
    "path": [
      {
        "position": "left",
        "hash": "9e246a6d6dda91ee3916e149d834cd9a140e3470fbf5b6ec67824705dbbc82d4"
      }
    ],
    "root": "88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
  },
  "anchors": [
    {
      "chain_id": 137,
      "network": "Polygon Mainnet",
      "tx_hash": "0xa4195d4ea808610dee92a1caa221d35f289e61674f451c82f7428e29813f9d6a",
      "block_number": 94653624,
      "payload": "sellat:v2:88fa1c4c3587d86d6f713e438c7ae745a91a6e03a5dd1b8282f17f35f30bfa14"
    }
  ],
  "attestations": [
    {
      "type": "opentimestamps",
      "state": "submitted"
    },
    {
      "type": "rfc3161-qualified-timestamp",
      "state": "issued"
    }
  ]
}

Missing something, or need more volume? Write to [email protected]: you will talk to the people who wrote the code.