Back to the blog

API de sello de tiempo: cómo certificar documentos desde tu aplicación

Respuesta JSON de la API de sello de tiempo de Sellat con el anclaje en Polygon y el sello cualificado de la FNMT

A timestamping API does one thing: it takes the SHA-256 hash of a file and fixes it to a date that no one can alter, so that two years from now you can prove that the exact same file already existed. Sellat API v2 does this in a single HTTP call: you send the hash (never the file) and receive a record anchored in Polygon and Bitcoin and, if requested, a qualified electronic timestamp issued by FNMT-RCM. The proof can be downloaded as JSON and verified without Sellat, using an open-source verifier or OpenSSL.

This guide is for anyone who wants to timestamp files from their own system rather than through a website: a SaaS platform that needs to prove when a user accepted something, a law firm automating case files, an online store preserving the exact version of its terms for each order, a system timestamping its logs every night, or an agency timestamping every deliverable before sending it. You will see what the API returns, how to integrate it in three calls, how to verify the proof without us and, above all, what it proves and what it does not.

What the API returns: the proof, layer by layer

The only thing that leaves your server is the SHA-256 hash of the file, a 64-character hexadecimal string. Sellat uses it to build a proof of existence made up of independent layers, each with its own date and authority:

  1. Merkle batch and Polygon anchoring. The hash enters a batch (with a maximum wait of two minutes), the Merkle root of that batch is calculated, and the root is written to a contract on Polygon Mainnet (chain_id 137). The proof stores the Merkle path and transaction details (tx_hash, block_number), which anyone can check using a public blockchain explorer.
  2. Bitcoin attestation. The same root is submitted to OpenTimestamps; the bitcoin field moves from pending to confirmed and includes its block_height once it is included in a block, usually within a few hours.
  3. Qualified electronic timestamp (optional). If you send "qualified": true, FNMT-RCM issues an RFC 3161 timestamp over the hash. It is issued immediately and independently of the blockchain anchoring, and can be downloaded as a DER-formatted .tsr file. This is the layer that benefits from a legal presumption in the EU; it is issued by FNMT, not Sellat.
  4. The portable proof. GET /proof/{id}.json is the only public endpoint: it returns the JSON using the sellat-proof/2 schema (hash, leaf, Merkle path, anchors and attestations), without authentication. This is what you can provide to a third party.
  5. Certificate and evidence package. A PDF in es, en, de or fr containing the hash, dates, anchors and timestamp, plus an evidence.zip containing the certificate, proof.json, the .tsr, the authority certificate and verification instructions. If you have deposited the original file in custody, it is included as well.

The status of the proof progresses automatically; there is nothing you need to do between calls:

StatusWhat it means
queuedReceived; waiting for a batch (maximum 2 minutes)
batchedIncluded in a Merkle batch with a fixed path; waiting for Polygon anchoring
anchoredAnchored in a Polygon block; proof.json is now available
confirmedThe block has 12 confirmations

The qualified timestamp and Bitcoin attestation have their own fields (qualified.state, bitcoin.state) and do not depend on this sequence.

Your first timestamp in three calls

You need an API key, which can be created and revoked from the dashboard (up to five active keys per account), and sent as Authorization: Bearer sellat_.... The API does not send CORS headers: it is intended to be called from your server, never from browser-side code, and the key should never leave your server.

1. Calculate the hash and create the proof. The file itself is not sent. The Idempotency-Key header ensures that retrying the request returns the same proof ("created": false) without consuming another timestamp.

export SELLAT_API_TOKEN=sellat_...
HASH=$(sha256sum contract.pdf | cut -d' ' -f1)

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

The response (201) includes the id, the initial queued status, the qualified block already set to issued with the FNMT-RCM authority, the timestamp time and the URL of the .tsr file, as well as the download URLs. If the account has no qualified timestamps available, the response is 402 payment_required with seals_remaining and buy_url; you can create the proof without qualified and request the timestamp later using POST /proofs/{id}/qualified.

2. Check the status until it is anchored. Within a few minutes it moves to anchored; there is no need to check more than once per minute.

curl https://sellat.app/api/v2/proofs/{id} \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"

3. Download the evidence package and store it with the file. The ZIP contains everything required to verify the proof without Sellat.

curl -L "https://sellat.app/api/v2/proofs/{id}/evidence.zip?lang=en" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" -o evidence.zip

If you also want Sellat to keep the original file in custody, use PUT /proofs/{id}/original with the file in the request body (10 MB per file on the free plan, 50 MB on Pro); the service recalculates the hash and rejects the deposit if it does not match (409 hash_mismatch). Any metadata you send is stored privately and is not published anywhere. There is also a command-line client, sellat-cli, which packages these three calls so you can timestamp logs or folders from a cron job, while the full specification is available in OpenAPI.

Verify without Sellat

A proof that can only be verified by the service that issued it is not much of a proof. That is why the format is public, the verifier is open source and the qualified timestamp can be validated using standard tools. Two commands:

# Anchor: recalculates the hash, follows the Merkle path and reads the Polygon block
curl https://sellat.app/api/v2/proof/{id}.json -o proof.json
npx sellat-verify contract.pdf proof.json

# Qualified timestamp: OpenSSL, using the authority certificate included in the ZIP
openssl ts -verify -in timestamp.tsr -data contract.pdf \
  -CAfile timestamp-authority.pem -partial_chain

The first command checks that the hash of the file in front of you is the one included in the root anchored on Polygon; the second checks that FNMT-RCM timestamped that same hash at the time stated in the timestamp and that the certificate belongs to the EU trusted framework. If you would rather not use the terminal, the timestamp validator performs all seven checks in the browser, without uploading the file, and accepts timestamps from any authority, not just those obtained through Sellat. The code is available at sellat-verify, together with the specification for the sellat-proof/2 format.

What it proves — and what it does not

It proves two things: that a file with exactly that hash existed at the time of the anchoring and timestamp, and that the file you present today has not changed by a single byte since then. It does not prove who created it, whether its contents are true, or whether another party received it. Other tools are used for that purpose (electronic signatures, certified email, expert evidence), and a solid evidence package will often combine several of them.

Regarding the legal framework, to be precise: the qualified electronic timestamp is issued by FNMT-RCM, a qualified trust service provider included in the EU trusted list; Sellat is not a qualified trust service provider and does not issue timestamps itself — it requests and delivers them. That qualified timestamp benefits from the presumption established by Article 41.2 of the eIDAS Regulation: the date and time it indicates and the integrity of the data to which it is bound are presumed to be accurate, and anyone challenging them must provide evidence to the contrary. The Polygon and Bitcoin anchoring does not benefit from that presumption; it is technical evidence that an expert can independently reproduce without having to trust Sellat and that remains verifiable even if Sellat or FNMT were to disappear.

That is why the proof always displays two separate dates: the anchoring date and the qualified timestamp date. Requesting the qualified timestamp later does not backdate anything: the timestamp records the time at which FNMT issued it, not the anchoring time. And if the same hash had already been registered earlier by another account, the proof is still created and remains valid, but it includes "limitations": ["hash_registered_by_another_account"] and no exclusive certificate: the API does not hide the fact that someone else registered it first.

Pricing, limits and when to use the API

Getting started costs nothing: the free plan allows 10 proofs per day per account (reset at 00:00 UTC), includes one complimentary qualified timestamp and 50 MB of custody storage. Polygon and Bitcoin anchoring is not charged per proof; what you pay for is the qualified timestamp, at €6 each or less when purchased in packs, and the Pro plan, which raises the limit to 5,000 proofs per day and custody storage to 5 GB. Current prices are available at sellat.app/precios; GET /account returns your remaining timestamps, used quota and custody storage at any time, allowing your system to know when it needs to alert you.

Two limits are worth knowing before designing your integration: 60 proof creations per minute and 10 qualified timestamp requests per minute per key (the API responds with 429 and Retry-After), and only one qualified timestamp request per proof. There are currently no batch endpoints or status-change webhooks: if you need to timestamp many files, send one hash per call; to know when a proof moves to anchored, query GET /proofs/{id} at a reasonable interval.

API, plugin or web? The web interface (sellat.app/protect) is designed for manually timestamping files one at a time. The WooCommerce plugin (coming in the next few days) solves one specific use case without requiring any development: preserving the exact terms and conditions displayed by the store for every order. The API is for everything else: when timestamping needs to happen inside an existing workflow, without anyone having to click a button.

Frequently asked questions

Do I have to send the file to Sellat? No. The API only receives the SHA-256 hash, which cannot be used to reconstruct the file’s contents. The file remains on your server; it only leaves your server if you choose to deposit it in custody using PUT /proofs/{id}/original.

How long does it take for the proof to be ready? The qualified timestamp is issued immediately. Polygon anchoring takes a few minutes (a batch of up to two minutes plus block inclusion) and moves to confirmed after 12 confirmations. The Bitcoin attestation is usually confirmed within a few hours. You can download the certificate as soon as the status reaches anchored.

Can it be used as evidence in court? Electronic evidence cannot be rejected solely because it is electronic. The qualified timestamp benefits from a presumption of accuracy and integrity throughout the EU (Article 41.2 eIDAS), while the blockchain anchoring can be independently reproduced by an expert. Neither layer, however, proves authorship or the truth of the file’s contents: those elements must be supported by other evidence in the case.

What happens if I register the same file twice? If it belongs to your account, the API returns the existing proof (200, "created": false) without consuming another timestamp. If another account registered that hash first, a new, valid and anchored proof is created, marked with limitations and without an exclusive certificate.

Do you have a file to protect?

Sellat creates a verifiable proof of the exact version of your file at the moment you protect it.

Protect files