Referencia de la API

API v2 de Sellat

Todo lo que hace la web, desde tu sistema: registrar la huella SHA-256 de un archivo, anclarla en Polygon y Bitcoin, pedir el sello de tiempo cualificado de la FNMT, descargar el certificado y el paquete de evidencias, y custodiar el original. REST y JSON, con una clave de API.

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

Introducción

La API trabaja con la huella del archivo, no con el archivo: calculas el SHA-256 en tu sistema y envías solo esos 64 caracteres. El original no sale de tu máquina salvo que decidas dejarlo en custodia.

Cada prueba entra en un lote Merkle anclado en Polygon, y el mismo lote se atesta en Bitcoin. Si lo pides, la huella recibe además el sello de tiempo cualificado de la FNMT-RCM, prestador cualificado de la lista de confianza de la UE.

Todas las rutas cuelgan de la URL base y hablan JSON. Las pruebas que creas por API y las que creas en la web son de la misma cuenta y aparecen en el mismo panel.

Está pensada para llamarse desde un servidor: no envía cabeceras CORS, así que un navegador no puede llamarla desde otra web, y tu clave nunca debe estar en código que llegue al navegador.

Autenticación

Cada petición lleva tu clave en la cabecera Authorization: Bearer sellat_.... Las claves se crean y se revocan desde tu panel; una cuenta puede tener hasta 5 activas, una por integración.

Sin clave, o con una clave revocada, la API responde 401 unauthorized con la cabecera WWW-Authenticate: Bearer. La única ruta pública es la prueba portable (/proof/{id}.json).

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

Inicio rápido

Calcula la huella, crea la prueba con el sello cualificado y guarda el id que te devuelve. En pocos minutos pasa a anchored y todas sus URLs funcionan.

Sin "qualified": true la prueba es igual de verificable (Polygon y Bitcoin) y no gasta un sello. Puedes pedir el sello más tarde con 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}"

Estados de una prueba

El campo state dice lo que Sellat sabe en cada momento: nunca marca como confirmada una transacción que todavía no ha comprobado.

El atestado en Bitcoin (bitcoin) va aparte: pending hasta que OpenTimestamps lo incluye en un bloque, y entonces confirmed con su altura. El sello cualificado (qualified) no depende del estado: se emite al momento.

EstadoQué significa
queuedRecibida. Espera a entrar en un lote, como mucho unos dos minutos.
batchedEn un lote Merkle, con su ruta fijada. Espera el anclaje en Polygon.
anchoredAnclada: la transacción está en un bloque de Polygon. Ya hay proof.json.
confirmedEl bloque tiene 12 confirmaciones.

Errores

Un error es siempre un JSON con error.code (estable, para tu código) y error.message (para personas). Algunos añaden campos, como seals_remaining y buy_url en un 402. La prueba portable, que es pública, responde {"error": "..."}.

Si la puerta de enlace corta antes de llegar a la API (demasiadas peticiones por IP, o un cuerpo de más de 25 MB), ese 429 o ese 413 no llevan este formato.

CódigoHTTPQué pasa
invalid_request400Falta un campo o no tiene el formato esperado. El mensaje dice cuál.
unauthorized401Sin clave, clave mal formada o revocada.
payment_required402No te quedan sellos cualificados. Trae seals_remaining y buy_url.
key_without_account403La clave no pertenece a una cuenta (claves antiguas).
not_found404La prueba no existe o es de otra cuenta.
conflict409Esos bytes los registró primero otra cuenta, o el original todavía no está guardado.
hash_mismatch409El archivo que subes no tiene la huella de la prueba.
file_too_large413El archivo supera el límite por archivo de tu plan (max_file_bytes).
rate_limited429Demasiadas peticiones por minuto. Respeta Retry-After.
quota_exceeded429Has llegado al límite diario de pruebas. Retry-After hasta las 00:00 UTC.
download_quota_exceeded429Has llegado al límite diario de descargas del original.
integrity_failed500El original guardado ya no coincide con su huella, así que no se entrega.
unavailable502 · 503Un servicio interno no responde. Reintenta más tarde.
seal_unavailable503La autoridad de sellado no está disponible ahora.
custody_unavailable503La custodia no está disponible en este momento.
custody_quota_exceeded507No queda espacio de custodia en tu cuenta.
Respuesta
{
  "error": {
    "code": "payment_required",
    "message": "No qualified seals left on this account.",
    "seals_remaining": 0,
    "buy_url": "https://sellat.app/precios"
  }
}

Límites

Cada cuenta puede crear un número de pruebas por API al día, sumando todas sus claves. Las pruebas que haces desde la web no cuentan, y volver a enviar bytes que ya son tuyos tampoco. El día se reinicia a las 00:00 UTC.

Al llegar al límite, POST /proofs responde 429 quota_exceeded con el objeto quota y Retry-After. Además, cada ruta tiene un freno por minuto (por ejemplo, 60 creaciones y 10 sellos por minuto) que responde 429 rate_limited, y la puerta de enlace admite hasta 120 peticiones por minuto por IP. Un cuerpo puede ocupar como mucho 25 MB.

Los sellos cualificados no tienen límite diario: cada uno gasta un sello de tu saldo. Consulta en cualquier momento lo que te queda con GET /account, y si necesitas más volumen, escríbenos a [email protected].

Cuenta gratuitaCuenta Pro
Pruebas por API al día105000
Claves activas55
Original en custodia, por archivo10 MB50 MB
Custodia total50 MB5 GB
Descargas del original al día1050
Respuesta
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
    }
  }
}

Idempotencia

Envía la cabecera Idempotency-Key (hasta 200 caracteres) en POST /proofs y podrás reintentar sin miedo: si ya la usaste con esa misma clave de API, recibes la prueba original con 200 y "created": false, diga lo que diga el cuerpo nuevo, y no se emite ningún sello.

Sin la cabecera, enviar otra vez unos bytes que ya son de tu cuenta también devuelve la prueba que existe (200), sin cambiar su nombre ni sus metadatos. Pero si esos bytes los registró primero otra cuenta, cada envío crea una prueba nueva con limitations: si puedes reintentar, usa siempre Idempotency-Key.

El sello tampoco se duplica: una prueba tiene un solo sello, y pedirlo otra vez devuelve el mismo sin gastar otro.

Verificar sin Sellat

Una prueba de Sellat no necesita a Sellat para comprobarse. Con el archivo y su proof.json, el verificador de código abierto recalcula la huella y la ruta Merkle, y lee el anclaje en la blockchain pública.

El sello cualificado se comprueba con OpenSSL, con el archivo, el .tsr y el certificado de la autoridad, que viene dentro del paquete de evidencias. -partial_chain hace falta porque la lista de confianza publica el certificado de la propia autoridad, no el de una CA.

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

Pruebas

Crear una prueba

POST /proofs

Registra la huella SHA-256 de un archivo y devuelve la prueba. Con "qualified": true emite además, en la misma llamada, el sello cualificado de la FNMT, y gasta un sello de la cuenta.

Parámetros

hash string · cuerpo obligatorio
La huella SHA-256 del archivo: 64 caracteres hexadecimales.
name string · cuerpo opcional
Cómo se llamará en el panel y en el certificado. Se guarda solo el nombre, sin la ruta. Por defecto, los 12 primeros caracteres de la huella.
metadata object · cuerpo opcional
Un objeto JSON tuyo, de hasta 4 KB. Se guarda con la prueba y no se devuelve ni se publica nunca.
size integer · cuerpo opcional
Tamaño del archivo en bytes. Informativo.
mime_type string · cuerpo opcional
Tipo del archivo. Se usa al servir el original si lo custodias. Por defecto, application/octet-stream.
qualified boolean · cuerpo opcional
true emite también el sello cualificado de la FNMT.
Idempotency-Key string · cabecera opcional
Para reintentar sin duplicar (ver Idempotencia).

Respuestas

201
Prueba creada.
200
Ya existía: esos bytes ya son de tu cuenta, o repetiste una Idempotency-Key. Trae "created": false.
400
invalid_request: un campo falta o no es válido.
401
unauthorized.
402
payment_required: pediste el sello y no te quedan. No se escribe nada.
409
conflict: pediste el sello sobre bytes que registró primero otra cuenta.
429
quota_exceeded (límite diario) o rate_limited (por minuto).
503
seal_unavailable o unavailable. Si la prueba llegó a crearse, el error trae su proof_id.

Si la autoridad tarda en responder, la prueba se crea igual y qualified llega como {"state": "pending"}: pídelo otra vez con POST /proofs/{id}/qualified, sin coste.

Si esos bytes los registró primero otra cuenta, la prueba se crea y se ancla, pero sin certificado propio: las URLs del certificado son null y la respuesta trae "limitations": ["hash_registered_by_another_account"].

Petición
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" }
  }'
Respuesta
{
  "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"
  }
}

Consultar una prueba

GET /proofs/{id}

Devuelve la prueba con su estado, sus anclajes, el atestado en Bitcoin, el sello si lo tiene, el original si está en custodia y todas sus URLs.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
La prueba.
401
unauthorized.
404
not_found: no existe o es de otra cuenta.
Petición
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "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"
  }
}

Listar pruebas

GET /proofs

Las pruebas de tu cuenta, de la más reciente a la más antigua: las creadas por API y las creadas en la web. Cada elemento de data es una prueba completa, como la de GET /proofs/{id}.

Para la página siguiente, pasa el next_cursor que te devolvió la anterior. Cuando es null, no hay más.

Parámetros

limit integer · query opcional
Cuántas pruebas por página, de 1 a 100. Por defecto, 25.
cursor string · query opcional
El next_cursor de la página anterior.

Respuestas

200
data y next_cursor.
400
invalid_request: limit o cursor no válidos.
401
unauthorized.
Petición
curl "https://sellat.app/api/v2/proofs?limit=25" \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "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"
}

Sello cualificado (eIDAS)

Pedir el sello cualificado

POST /proofs/{id}/qualified

Emite un sello de tiempo RFC 3161 de la FNMT-RCM sobre la huella del archivo. Según el art. 41.2 del Reglamento eIDAS, un sello de tiempo cualificado goza de la presunción de exactitud de su fecha y hora y de integridad de los datos.

Gasta un sello de la cuenta: el de bienvenida o uno de tus packs. La API nunca cobra una tarjeta ni abre un pago; los packs se compran en la página de precios.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
Sello emitido, o ya lo tenía ("created": false, sin gastar otro). Trae seals_remaining.
202
El sello se ha pagado, pero la autoridad no ha respondido. Llama otra vez (Retry-After: 30): se completa sin coste.
401
unauthorized.
402
payment_required: no te quedan sellos. Trae seals_remaining y buy_url.
404
not_found.
409
conflict: esos bytes los registró primero otra cuenta.
503
seal_unavailable.

Una prueba tiene un solo sello, para siempre. Si la emisión falla del todo, el sello vuelve a tu saldo.

Petición
curl -X POST https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "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
}

Consultar el sello

GET /proofs/{id}/qualified

Devuelve solo el sello de la prueba, o null si todavía no tiene. No emite nada ni gasta sellos.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
proof_id y qualified.
401
unauthorized.
404
not_found.
Petición
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "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"
  }
}

Descargar el token (.tsr)

GET /proofs/{id}/qualified.tsr

El sello tal como lo devolvió la autoridad: una respuesta RFC 3161 (TimeStampResp) en DER. Es el archivo que un perito o un tribunal puede validar sin Sellat.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
El .tsr (application/timestamp-reply).
401
unauthorized.
404
not_found: la prueba no existe o todavía no tiene sello.
Petición
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/qualified.tsr \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o seal.tsr
Respuesta (archivo)
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>

Certificado y evidencias

Certificado en PDF

GET /proofs/{id}/certificate.pdf

El certificado de la prueba, para personas: la huella, la fecha, los anclajes y el sello cualificado si lo tiene, con las instrucciones para verificarlo.

Parámetros

id string · ruta obligatorio
El id de la prueba.
lang string · query opcional
Idioma del certificado: es, en, de o fr. Cualquier otro valor da en.

Respuestas

200
El PDF.
401
unauthorized.
404
not_found.
409
conflict: esos bytes los registró primero otra cuenta y la prueba no tiene certificado propio.
Petición
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
Respuesta (archivo)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="certificado-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.pdf"

<PDF>

Paquete de evidencias

GET /proofs/{id}/evidence.zip

Un ZIP con lo necesario para defender la prueba sin Sellat: el certificado, el proof.json, el sello y el certificado de la autoridad si lo tiene, las instrucciones y, si está en custodia, el original.

Parámetros

id string · ruta obligatorio
El id de la prueba.
lang string · query opcional
Idioma de los textos del paquete: es, en, de o fr.

Respuestas

200
El ZIP.
401
unauthorized.
404
not_found.
409
conflict: esos bytes los registró primero otra cuenta.

Incluir el original gasta una de las descargas del día. Si ya no te quedan, el paquete sale sin él y el README lo dice.

Petición
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
Respuesta (archivo)
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="sellat-evidencia-3b1d7e42-9c5a-4f0e-b8d6-1a2c3e4f5a6b.zip"

<ZIP>

Custodia del original

Depositar el original

PUT /proofs/{id}/original

Guarda el archivo junto a su prueba, en servidores de la Unión Europea. Sellat vuelve a calcular la huella de lo que recibe y lo rechaza si no coincide con la prueba.

Envía los bytes tal cual, con el Content-Type del archivo, o como multipart/form-data en el campo file. POST funciona igual que PUT.

Parámetros

id string · ruta obligatorio
El id de la prueba.
file binary · cuerpo obligatorio
El archivo. Hasta 10 MB con la cuenta gratuita y 50 MB con la Cuenta Pro, y como mucho 25 MB por petición.

Respuestas

201
Guardado. Devuelve la prueba con original.
200
Ya estaba guardado.
400
invalid_request: el cuerpo está vacío o no se puede leer.
401
unauthorized.
404
not_found.
409
hash_mismatch (no es el archivo de la prueba) o conflict (bytes de otra cuenta).
413
file_too_large: supera el límite por archivo de tu plan. Trae max_file_bytes.
502
unavailable: el almacenamiento no respondió.
503
custody_unavailable.
507
custody_quota_exceeded: no queda espacio en tu cuenta. Trae used_bytes y max_account_bytes.

La custodia es opcional: la prueba vale igual sin el original. Custodiarlo añade conservación, no la condiciona.

Petición
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
Respuesta
{
  "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"
  }
}

Descargar el original

GET /proofs/{id}/original

Devuelve el archivo custodiado. Sellat recalcula su huella al servirlo y no lo entrega si ya no coincide. Cada descarga cuenta para el límite diario de tu plan; la cabecera X-Sellat-Custody-Downloads-Remaining dice cuántas te quedan.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
El archivo, con su tipo y su nombre.
401
unauthorized.
404
No hay original en custodia.
409
conflict: el original todavía no está guardado.
429
download_quota_exceeded: límite diario de descargas. Se reinicia a las 00:00 UTC.
500
integrity_failed: lo guardado ya no coincide con la huella.
502
unavailable.
Petición
curl https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN" \
  -o contract.pdf
Respuesta (archivo)
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="contract.pdf"
X-Sellat-Custody-Downloads-Remaining: 9

<the original bytes>

Terminar la custodia

DELETE /proofs/{id}/original

Borra el original custodiado. La prueba no cambia: sigue anclada, sellada y verificable.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
"original": null y "proof_unaffected": true. "purged": false quiere decir que el borrado físico está en cola.
401
unauthorized.
404
No hay original en custodia.
Petición
curl -X DELETE https://sellat.app/api/v2/proofs/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e/original \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "proof_id": "6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e",
  "original": null,
  "purged": true,
  "proof_unaffected": true
}

Cuenta

Tu cuenta

GET /account

Lo que puede hacer la clave que llama: el plan, los sellos que te quedan (de bienvenida y de packs), el espacio y las descargas de custodia, las pruebas de hoy y su límite, y las claves activas. Consúltalo antes de un lote grande.

El bloque connector son los límites de las tiendas conectadas (WooCommerce, próximamente).

Respuestas

200
La cuenta.
401
unauthorized.
Petición
curl https://sellat.app/api/v2/account \
  -H "Authorization: Bearer $SELLAT_API_TOKEN"
Respuesta
{
  "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"
  }
}

Prueba portable

Prueba portable (proof.json)

GET /proof/{id}.json Sin clave

El documento que permite verificar la prueba sin Sellat, en el formato público sellat-proof/2: la huella, la hoja, la ruta Merkle, la raíz, el anclaje en Polygon y los atestados (Bitcoin y el sello cualificado). Es público a propósito: quien lo tenga puede comprobar la prueba sin cuenta.

Parámetros

id string · ruta obligatorio
El id de la prueba.

Respuestas

200
El proof.json.
202
Todavía no está anclada: {"ready": false, "state": "..."} con Retry-After: 60.
400
El id no tiene formato de UUID.
404
No existe.
Petición
curl https://sellat.app/api/v2/proof/6f2c9b1e-0d1a-4b9f-8e7c-2a4d5b6c7d8e.json -o proof.json
Respuesta
{
  "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"
    }
  ]
}

¿Te falta algo o necesitas más volumen? Escríbenos a [email protected]: hablarás con quien ha escrito el código.