Base URLhttps://api.sigill.ai
AuthenticationAuthorization: Bearer <token>
Formatsapplication/json

JWT (from /auth/login) and API key (from Settings → API Keys) are both accepted as Bearer tokens. API keys are recommended for scripts and CI.

Errors use RFC 7807 application/problem+json, with status, title, and a human-readable detail. Resource-not-found responses may be an empty 404.

Authentication

POST/auth/registerpublic

Create a new user account and a new organisation tenant. Existing organisations are joined through an invitation or verified identity-provider flow, never by supplying a matching email domain at signup.

Request body
{
  "name": "string",
  "email": "string",
  "password": "string (min 8)",
  "organisationName": "string (optional)"
}
Response 200
{
  "token": "<jwt>",
  "userId": "uuid",
  "email": "string",
  "tenantId": "uuid",
  "role": "owner"
}
POST/auth/loginpublic

Authenticate and receive a JWT.

Request body
{
  "email": "string",
  "password": "string"
}
Response 200
{
  "token": "<jwt>",
  "userId": "uuid",
  "email": "string",
  "tenantId": "uuid|null"
}
POST/auth/refreshJWT

Re-issue a JWT with the latest tenant context. Call after creating an organisation.

Response 200
{
  "token": "<new jwt>"
}
GET/auth/meJWT

Return the current user's profile decoded from the JWT.

Timestamping

Timestamp and verification endpoints accept digests and TSR evidence only. Deprecated full-file stamp/verify and raw TSA proxy endpoints have been removed.

POST/tsa/stamp-hashJWT / API key

Timestamp a document by its hash. Compute SHA-256 (or SHA-384 / SHA-512) of the file locally — only the digest is sent to Sigill. The file never leaves your machine. This is how RFC 3161 is meant to work.

Request body
{
  "hashHex": "hex digest — SHA-256 (64 chars), SHA-384 (96), or SHA-512 (128)",
  "tsaSlug": "qualified=false: auto | byot:<id-or-slug>; qualified=true: qualified option where available",
  "label": "string (optional)",
  "force": "boolean — re-stamp an already-stamped hash (default false)",
  "qualified": "boolean — request an eIDAS-qualified timestamp (default false)"
}
Response 200
{
  "serial": "string",
  "genTime": "ISO 8601",
  "hashAlgorithmOid": "OID string",
  "hashHex": "hex string",
  "tsrBase64": "base64 .tsr token",
  "tsaName": "string — timestamp token issuer metadata or customer authority label",
  "qualified": "boolean",
  "policyOid": "string | null — present when qualified: true"
}
Standard stamps use Sigill-managed automatic RFC 3161 timestamping when tsaSlug is "auto". Sigill may use multiple timestamp sources internally for reliability and failover; customers do not select individual Sigill-managed standard sources. Standard mode never routes to an eIDAS qualified option unless qualified: true is explicitly sent, and a standard response carrying a qualified policy is rejected rather than returned or stored. See Automatic TSA selection. Use force: true to create a second independent proof for a file that has already been stamped.

You can also point tsaSlug at one of your verified tenant-private BYOT references from Timestamp Authorities when you need to use a customer-managed TSA endpoint.

Qualified stamps (qualified: true) route exclusively to eIDAS Qualified Trust Service Providers on the EU Trust List. Qualified stamps are counted separately: lower plans receive three lifetime trial credits, while Business and Scale use their monthly qualified quota.
Response 502 — all TSAs failed (auto)
{
  "message": "All Sigill-managed timestamp sources failed.",
  "attemptsTried": 3,
  "failures": [
    {
      "source": "standard-pool-1",
      "errorClass": "timeout",
      "statusCode": null,
      "message": "Request timed out after 10s",
      "latencyMs": 10042
    }
  ]
}
Response 402 — quota exceeded
{
  "message": "Monthly stamp limit of 50 reached. Upgrade your plan to continue."
}
POST/tsa/restampJWT / API key

Archival restamp per RFC 3161 §4. Hashes the existing TSR bytes (not the original file) and stamps them with standard RFC 3161 timestamping, creating a timestamp chain. Responses carrying a qualified policy are rejected on this standard-only route. Use before a timestamp authority certificate expires.

Request body
{
  "transactionId": "uuid — the record to restamp",
  "tsaSlug": "auto | byot:<id-or-slug>"
}
Response 200
{
  "id": "uuid",
  "parentTransactionId": "uuid",
  "tsaName": "string",
  "genTime": "ISO 8601"
}
Use tsaSlug: "auto" for Sigill-managed standard timestamping. Use a verified BYOT timestamp authority when you need to restamp through a customer-managed TSA endpoint.
POST/tsa/verify-hashpublic

Verify a file against a TSR by its hash. The server validates the TSR token signature and timestamping certificate usage as well as the hash match. Call /tsa/inspect first to find the recorded hash algorithm, then compute that hash locally. The file never leaves your machine.

Request body
{
  "fileHashHex": "hex digest of the file (algorithm from /tsa/inspect)",
  "tsrBase64": "base64 .tsr token"
}
Response 200
{
  "valid": true,
  "message": "File matches the timestamp.",
  "details": {
    "serial": "string",
    "genTime": "ISO 8601",
    "hashAlgorithmOid": "string",
    "claimedHashHex": "string",
    "providedHashHex": "string",
    "hashMatch": true,
    "tokenSignatureValid": true
  }
}
POST/tsa/inspectpublic

Parse and return the contents of a TSR token without verifying against a file.

Request body
{
  "tsrBase64": "base64 .tsr token"
}
Response 200
{
  "genTime": "ISO 8601",
  "serial": "string",
  "hashAlg": "OID string",
  "tsaName": "string",
  "certNotBefore": "ISO 8601",
  "certNotAfter": "ISO 8601"
}

Automatic TSA selection

Pass tsaSlug: "auto" to /tsa/stamp-hash or /tsa/restamp for Sigill-managed standard RFC 3161 timestamping. Seal operations with qualified: false via /seal/sign use the same path; passing qualified: true embeds a qualified RFC 3161 timestamp inside the seal — the seal itself remains an advanced electronic seal (AdES) regardless. Verified tenant-private BYOT authorities may be selected explicitly when you need to use a customer-managed endpoint.

How it works

  • Managed standard pool. Sigill may use multiple timestamp sources internally for reliability and failover. The exact internal source is not customer-selectable unless you configure BYOT.
  • Failover on the same request. If an internal source fails, Sigill can try another source before returning an error. The caller only sees the final outcome.
  • BYOT stays customer-managed. Verified tenant-private timestamp authorities are selected by their BYOT reference. If one returns a token carrying a qualified policy, standard mode rejects that response.
  • Every internal failure is logged. Errors are stored with classification (network, timeout, http_status, parse) for Sigill operations and backoffice health monitoring.

When to use auto

Default to auto in production. Use a verified BYOT timestamp authority when you need to use a customer-managed TSA endpoint.

Error response when all TSAs fail

If every Sigill-managed standard source fails within the same request, you get a 502 Bad Gateway with anonymized failure details:

{
  "message": "All Sigill-managed timestamp sources failed.",
  "attemptsTried": 3,
  "failures": [
    {
      "source": "standard-pool-1",
      "errorClass": "timeout",
      "statusCode": null,
      "message": "Request timed out after 10s",
      "latencyMs": 10042
    },
    {
      "source": "standard-pool-2",
      "errorClass": "http_status",
      "statusCode": 503,
      "message": "service unavailable",
      "latencyMs": 412
    },
    {
      "source": "standard-pool-3",
      "errorClass": "parse",
      "statusCode": 200,
      "message": "malformed TSR: unexpected ASN.1 tag",
      "latencyMs": 980
    }
  ]
}
The errorClass values are stable strings you can match in client code: network (connection refused, DNS, TLS), timeout (HTTP client timeout), http_status (TSA responded non-2xx), parse (response wasn't a valid TSR), unexpected_qualified (a standard candidate returned a qualified-policy token), unknown (catch-all).

Lookup

GET/api/lookup/{hash}public

Check if a file hash has been stamped. The hash must be a SHA-512 hex string. This endpoint is public — no authentication required.

Path parameter
hash  SHA-512 hex string (128 chars)
Response 200
{
  "found": true,
  "count": 2,
  "latest": {
    "id": "uuid",
    "hash": "hex string",
    "alg": "OID string",
    "tsaName": "string",
    "genTime": "ISO 8601",
    "certNotAfter": "ISO 8601",
    "hasTsr": true
  },
  "records": [
    "same public cryptographic fields"
  ]
}

Timestamps

GET/api/transactions/detailsJWT / API key

Paginated list of timestamp records for a tenant, with optional search. Each item includes hasRestamp so clients can identify records whose archival renewal chain has already advanced.

Query parameters
tenantId   uuid (required)
page       integer (default 1)
pageSize   integer (default 50, max 200)
search     string — filters on label or hash prefix (optional)
Response 200
{
  "total": 42,
  "page": 1,
  "pageSize": 50,
  "items": [
    {
      "id": "uuid",
      "parentTransactionId": null,
      "isRestamp": false,
      "hasRestamp": true,
      "certNotAfter": "ISO 8601"
    }
  ]
}
PATCH/api/transactions/{id}/labelJWT / API key

Set or update the label on a timestamp record.

Request body
{
  "label": "string (max 255)"
}
Response 200
{
  "id": "uuid",
  "label": "string"
}

Timestamp Authorities

GET/proxy/servicesJWT / API key

List customer-selectable timestamping options available to the caller's tenant. Sigill-managed standard RFC 3161 timestamping is selected with tsaSlug: "auto" and internal standard sources are not returned here. Verified tenant-private BYOT authorities are returned as source: "tenant" entries; qualified timestamping options may appear as source: "qualified" when entitled.

Response 200
[
  {
    "id": "uuid",
    "name": "My timestamp authority",
    "proxySlug": "byot:my-tsa",
    "preferredHashOid": "2.16.840.1.101.3.4.2.3",
    "isQualified": false,
    "source": "tenant"
  },
  {
    "id": "uuid",
    "name": "Qualified timestamping",
    "proxySlug": "qualified",
    "preferredHashOid": "2.16.840.1.101.3.4.2.3",
    "isQualified": true,
    "source": "qualified"
  }
]
The underlying Sigill-managed standard timestamping pool is internal infrastructure. Customers do not select individual non-qualified Sigill-managed timestamp sources.

Timestamp Authorities

Paid-plan tenants can add their own RFC 3161 endpoints under Settings → Timestamp authorities. Sigill verifies each configuration server-side before saving it, protects stored credentials with Data Protection, and exposes verified entries as tenant-private BYOT references.

GET/api/tenant/tsaJWT

List the authenticated tenant's BYO TSA configurations. Returns the verification status and the last successful test result for each row.

Response 200
{
  "planAllows": true,
  "plan": "starter",
  "maxPerTenant": 10,
  "items": [
    {
      "id": "uuid",
      "label": "My TSA",
      "slug": "my-tsa",
      "endpointUrl": "https://tsa.example.com",
      "preferredHashOid": "2.16.840.1.101.3.4.2.3",
      "authType": "basic",
      "username": "tsa-user",
      "hasSecret": true,
      "isActive": true,
      "verifiedAt": "ISO 8601",
      "lastVerifySerialHex": "string",
      "lastVerifyTsaName": "string"
    }
  ]
}
POST/api/tenant/tsa/verifyJWT

Run a one-shot RFC 3161 probe against a candidate endpoint without saving it. This is the same server-side verification step used by Save.

Request body
{
  "endpointUrl": "https://tsa.example.com",
  "preferredHashOid": "2.16.840.1.101.3.4.2.3",
  "authType": "none | basic | bearer",
  "username": "string (basic only)",
  "secret": "string (password or bearer token)"
}
Response 200
{
  "ok": true,
  "serial": "string",
  "genTime": "ISO 8601",
  "hashAlgorithmOid": "OID string",
  "tsaName": "string",
  "lotlQualified": false
}
POST/api/tenant/tsaJWT

Create a BYO TSA after the server verifies it. Free-plan tenants get a 402. The configuration is saved only after the endpoint passes the live RFC 3161 check.

Request body
{
  "label": "My timestamp authority",
  "slug": "my-tsa",
  "endpointUrl": "https://tsa.example.com",
  "preferredHashOid": "2.16.840.1.101.3.4.2.3",
  "authType": "none | basic | bearer",
  "username": "string (basic only)",
  "secret": "string (password or bearer token)",
  "responsibilityConfirmed": true
}
PATCH/api/tenant/tsa/:idJWT

Update a BYO TSA. Transport changes trigger a fresh verification before the row is committed.

DELETE/api/tenant/tsa/:idJWT

Remove a BYO TSA. This remains available even after a plan downgrade so tenants can clean up their configuration.

Verified tenant timestamp authorities can be selected explicitly as tsaSlug values using their slug or byot:<slug>. BYOT endpoints are customer-managed; Sigill verifies technical RFC 3161 compatibility, not the customer's right to use the endpoint.

Document Seal

Cryptographic seals backed by KMS — the private key never leaves. An RFC 3161 timestamp is embedded in every seal automatically. Owner role required to manage certificates; any authenticated user can seal.

Two signing paths are available depending on whether you need PDF embedding or privacy-preserving signing:

  • POST /seal/sign — uploads the full file. PDFs receive a PAdES signature (ETSI EN 319 142-1) embedded in the file. Primarily intended for PDF sealing.
  • POST /seal/sign-hash — accepts a pre-computed SHA-256 digest only. Non-PDF files (e.g. JSONL, XML, plain text) receive a detached CAdES .p7s without the original content ever leaving the client. Same privacy model as /tsa/stamp-hash.
GET/seal/certificatesJWT / API key

List active signing certificates for the tenant. The Sigill platform certificate is always appended at the end with source: "platform".

Response 200
[
  {
    "id": "uuid",
    "label": "string",
    "status": "active | provisioning | revoked",
    "source": "byoc | platform",
    "certSubject": "CN=...",
    "certNotBefore": "ISO 8601",
    "certNotAfter": "ISO 8601"
  }
]
POST/seal/certificatesJWT (owner)

Provision a new signing key. Generates RSA-4096 in KMS and returns a PKCS#10 CSR for CA submission. Submit the CSR to a CA, then activate via the endpoint below.

Request body
{
  "commonName": "Acme Corp Seal 2026",
  "organization": "Acme Corp AS",
  "countryCode": "NO",
  "organizationalUnit": "string (opt)",
  "locality": "string (opt)",
  "state": "string (opt)",
  "label": "string (opt)"
}
Response 200
{
  "id": "uuid",
  "label": "string",
  "status": "provisioning",
  "kmsKeyArn": "arn:aws:kms:...",
  "csrPem": "-----BEGIN CERTIFICATE REQUEST-----..."
}
POST/seal/certificates/:id/activateJWT (owner)

Upload the CA-signed certificate chain PEM. Validates that the public key matches the provisioned KMS key before activating.

Request body
{
  "certificatePem": "PEM certificate chain: leaf certificate plus optional intermediates"
}
Response 200
{
  "id": "uuid",
  "status": "active",
  "certSubject": "CN=...",
  "certNotBefore": "ISO 8601",
  "certNotAfter": "ISO 8601"
}
422 Unprocessable if the certificate's public key does not match the KMS key, or if the certificate is already expired.
DELETE/seal/certificates/:idJWT (owner)

Revoke a certificate and schedule the KMS key for deletion (7-day grace period). Cannot be undone. Platform certificate cannot be revoked via this endpoint.

Response 200
{
  "message": "Certificate revoked. KMS key deletion scheduled."
}
POST/seal/signJWT / API key

Seal a file by uploading it. Accepts multipart/form-data. File type is detected server-side from magic bytes — PDFs produce a PAdES-signed PDF (application/pdf); other files produce a detached CAdES .p7s (application/pkcs7-signature). For non-PDF CAdES, pqc: true adds an ML-DSA-87 signer in the same CMS. For non-PDF files where privacy matters, prefer POST /seal/sign-hash instead — it never receives the file content.

Form fields
{
  "file": "file to seal (required)",
  "certificateId": "uuid (required)",
  "label": "string — stored in operation log (opt)",
  "reason": "PDF only — written into PDF /Reason field (opt)",
  "location": "PDF only — written into PDF /Location field (opt)",
  "qualified": "boolean — embed a qualified RFC 3161 timestamp (default false)",
  "pqc": "boolean — CAdES only, add ML-DSA-87 signer (default false)"
}
Response headers
{
  "X-Seal-Operation-Id": "uuid",
  "X-Seal-Certificate-Id": "uuid",
  "X-Seal-Timestamped-By": "TSA name | none",
  "X-Seal-Format": "pades-b-lta | pades-b-lt | pades-b-t | pades-bes | cades-x-l | cades-t | cades-bes",
  "X-Seal-Qualified": "true | false",
  "X-Seal-Pqc": "ml-dsa-87 | none"
}
PDF response — sealed PDF (application/pdf), save as filename_sealed.pdf.

Non-PDF response — detached CAdES signature (application/pkcs7-signature), save as filename.p7s alongside the original file.

If every Sigill-managed TSA fails, the seal is still produced but X-Seal-Timestamped-By is none and the format degrades to *-bes.
POST/seal/sign-hashJWT / API key

Seal a non-PDF file by sending only its digest. The original file content never leaves the client — same privacy model as /tsa/stamp-hash. For pqc: true, send both SHA-256 (hashHex) for the classical signer and SHA-512 (hashHex512) for the ML-DSA signer. Returns a detached CAdES .p7s that verifies against the original file.

Request body (JSON)
{
  "hashHex": "64-char lowercase SHA-256 hex of the file (required)",
  "certificateId": "uuid (required)",
  "label": "string — stored in operation log (opt)",
  "qualified": "boolean — embed a qualified RFC 3161 timestamp (default false)",
  "pqc": "boolean — add ML-DSA-87 signer (default false)",
  "hashHex512": "128-char SHA-512 hex of same file; required when pqc=true"
}
Response headers
{
  "X-Seal-Operation-Id": "uuid",
  "X-Seal-Certificate-Id": "uuid",
  "X-Seal-Timestamped-By": "TSA name | none",
  "X-Seal-Format": "cades-x-l | cades-t | cades-bes",
  "X-Seal-Qualified": "true | false",
  "X-Seal-Pqc": "ml-dsa-87 | none"
}
Response body is a detached CAdES .p7s (application/pkcs7-signature). Save it alongside the original file. To verify, supply both the original file and the .p7s to POST /seal/verify — the verifier recomputes the hash and checks it matches the digest embedded in the signature.

400 if hashHex is not a valid 64-character hex string. PAdES (PDF) sealing is not supported on this endpoint — use /seal/sign for PDFs.
GET/seal/operationsJWT / API key

Paginated seal history for the authenticated tenant.

Query params
{
  "page": 1,
  "pageSize": 50,
  "search": "label prefix or hash prefix (opt)"
}
Response 200
{
  "total": 42,
  "page": 1,
  "pageSize": 50,
  "items": [
    {
      "id": "uuid",
      "documentHash": "hex",
      "label": "string",
      "status": "success",
      "createdAt": "ISO 8601",
      "certLabel": "string",
      "signatureType": "pades | cades",
      "hasP7s": "boolean — true when .p7s is stored and downloadable",
      "tsaName": "string | null"
    }
  ]
}
GET/seal/operations/:id/p7sJWT / API key

Download the stored CAdES .p7s for a seal operation. Only available when signatureType is "cades" and the tenant has Store CAdES seals enabled in Settings. Returns application/pkcs7-signature.

Response 200
.p7s file bytes (binary)
Response 404
{
  "message": ".p7s not stored — enable Store CAdES seals in Settings, or download immediately after sealing."
}
POST/seal/verifypublic

Verify a sealed document. Accepts multipart/form-data. Routes automatically — if a p7s field is present, performs CAdES verification against the original file; otherwise treats file as a sealed PDF and performs PAdES verification.

Form fields
{
  "file": "original file or sealed PDF (required)",
  "p7s": ".p7s detached signature — CAdES path only (opt)",
  "tsr": "standalone .tsr token for external timestamp verification (opt)"
}
Response 200 — PAdES
{
  "format": "pades",
  "pades": {
    "signaturePresent": true,
    "hashMatch": true,
    "certificate": {
      "subject": "CN=Acme Corp Seal",
      "trust": "chained | dev_ca | self_signed",
      "qc": {
        "isEidasQualified": false
      }
    },
    "timestamp": {
      "genTime": "ISO 8601",
      "tsaName": "string",
      "qc": {
        "isEidasQualified": true
      }
    }
  }
}
Response 200 — CAdES
{
  "format": "cades",
  "cades": {
    "signaturePresent": true,
    "hashMatch": true,
    "fileHashHex": "hex",
    "certificate": {
      "subject": "CN=Acme Corp Seal",
      "trust": "chained"
    },
    "timestamp": {
      "genTime": "ISO 8601",
      "tsaName": "string"
    },
    "postQuantum": {
      "present": true,
      "valid": true,
      "algorithm": "ml-dsa-87",
      "signatureValid": true,
      "contentBound": "yes | no | not_checked",
      "trusted": "yes | no | not_evaluated"
    },
    "tsrSource": "embedded | external | null",
    "tsrMatchError": null,
    "error": null
  }
}
Error responses
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "Bad Request",
  "status": 400,
  "detail": "No file provided."
}
POST/seal/verify-hashpublic

Verify a detached CAdES seal using document hashes only. For hybrid PQC seals, include hashHex512 to check the ML-DSA signer's SHA-512 content binding. Without it, the PQC signature can still be checked but postQuantum.contentBound is "not_checked".

Request body
{
  "hashHex": "SHA-256/384/512 hex for the classical signer",
  "hashHex512": "128-char SHA-512 hex for PQC binding (optional)",
  "p7sBase64": "base64 .p7s",
  "tsrBase64": "base64 standalone TSR (optional)"
}
Response 200
{
  "format": "cades",
  "cades": {
    "signaturePresent": true,
    "hashMatch": true,
    "signatureValid": true,
    "postQuantum": {
      "present": true,
      "valid": false,
      "algorithm": "ml-dsa-87",
      "signatureValid": true,
      "contentBound": "not_checked",
      "trusted": "no | not_evaluated"
    }
  }
}

Code examples

Stamp a file with curl

# 1. Hash your file locally — the file never leaves your machine
HASH=$(sha256sum yourfile.pdf | awk '{print $1}')

# 2. Stamp the hash — "auto" uses Sigill-managed standard
# RFC 3161 timestamping with redundancy and failover.
curl -X POST https://api.sigill.ai/tsa/stamp-hash \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "hashHex": "'"$HASH"'",
    "tsaSlug": "auto",
    "label": "yourfile.pdf"
  }' | jq .

# 3. Save the .tsr token
curl -X POST https://api.sigill.ai/tsa/stamp-hash \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"hashHex":"'"$HASH"'","tsaSlug":"auto"}' \
  | jq -r .tsrBase64 | base64 -d > yourfile.tsr

Stamp a file with Python

import hashlib, base64, httpx

API_KEY = "your_api_key"
BASE_URL = "https://api.sigill.ai"

# Hash the file locally — it never leaves your machine
with open("yourfile.pdf", "rb") as f:
    hash_hex = hashlib.sha256(f.read()).hexdigest()

resp = httpx.post(
    f"{BASE_URL}/tsa/stamp-hash",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        # "auto" uses Sigill-managed standard RFC 3161 timestamping.
        # Use a verified BYOT reference when you need a customer-managed TSA endpoint.
        "hashHex": hash_hex,
        "tsaSlug": "auto",
        "label": "yourfile.pdf",
    },
)
resp.raise_for_status()
data = resp.json()

# Save the .tsr token
tsr_bytes = base64.b64decode(data["tsrBase64"])
with open("yourfile.tsr", "wb") as f:
    f.write(tsr_bytes)

# data["tsaName"] reports timestamp token issuer metadata or your BYOT label
print(f"Stamped at {data['genTime']} by {data['tsaName']}")

Stamp a file with Node.js

import fs from "fs";
import crypto from "crypto";
import fetch from "node-fetch";

const API_KEY = "your_api_key";
const BASE_URL = "https://api.sigill.ai";

// Hash the file locally — it never leaves your machine
const hashHex = crypto
  .createHash("sha256")
  .update(fs.readFileSync("yourfile.pdf"))
  .digest("hex");

const res = await fetch(`${BASE_URL}/tsa/stamp-hash`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    // "auto" = Sigill-managed standard timestamping with failover. Recommended for production.
    hashHex,
    tsaSlug: "auto",
    label:   "yourfile.pdf",
  }),
});

if (res.status === 502) {
  // All TSAs failed — inspect the structured failure list
  const err = await res.json();
  console.error(`${err.attemptsTried} TSA(s) failed:`, err.failures);
  process.exit(1);
}

const data = await res.json();

// Save the .tsr token
const tsr = Buffer.from(data.tsrBase64, "base64");
fs.writeFileSync("yourfile.tsr", tsr);

console.log(`Stamped at ${data.genTime} by ${data.tsaName}`);

Seal a non-PDF file (CAdES) with curl

For PDFs the response is a signed PDF; for any other file you get a .p7s detached signature. Check X-Seal-Format to branch.

# Seal any file — PDF gets PAdES, everything else gets CAdES .p7s
CERT_ID="<your-certificate-uuid>"
FILE="report.json"

RESPONSE=$(curl -s -D - -o /tmp/sealed_output   -X POST https://api.sigill.ai/seal/sign   -H "Authorization: Bearer YOUR_API_KEY"   -F "file=@$FILE"   -F "certificateId=$CERT_ID"   -F "label=$FILE"   -F "qualified=false")

FORMAT=$(echo "$RESPONSE" | grep -i "x-seal-format:" | tr -d '
' | awk '{print $2}')

if [ "$FORMAT" = "cades" ]; then
  # Non-PDF: response is a detached .p7s signature
  # Keep both the original file and the .p7s — you need both to verify
  mv /tmp/sealed_output "${FILE%.json}.p7s"
  echo "CAdES seal saved: ${FILE%.json}.p7s"
  echo "Keep alongside original: $FILE"
else
  # PDF: response is the sealed PDF with embedded signature
  mv /tmp/sealed_output "${FILE%.pdf}_sealed.pdf"
  echo "PAdES seal saved: ${FILE%.pdf}_sealed.pdf"
fi

Seal a non-PDF file (CAdES) with Python

import httpx, pathlib

API_KEY  = "your_api_key"
BASE_URL = "https://api.sigill.ai"
CERT_ID  = "<your-certificate-uuid>"
file_path = pathlib.Path("report.json")

with open(file_path, "rb") as fh:
    resp = httpx.post(
        f"{BASE_URL}/seal/sign",
        headers={"Authorization": f"Bearer {API_KEY}"},
        files={"file": (file_path.name, fh, "application/octet-stream")},
        data={
            "certificateId": CERT_ID,
            "label": file_path.name,
            "qualified": "false",
        },
        timeout=60,
    )

resp.raise_for_status()
fmt = resp.headers.get("x-seal-format", "pades")

if fmt == "cades":
    # Non-PDF: detached CAdES .p7s — store alongside the original file
    out = file_path.with_suffix(".p7s")
    out.write_bytes(resp.content)
    print(f"CAdES seal → {out}")
    print(f"Keep original file: {file_path}")
    print(f"Verify: POST /seal/verify with file={file_path.name} + p7s={out.name}")
else:
    # PDF: sealed PDF with embedded PAdES signature
    out = file_path.with_stem(file_path.stem + "_sealed").with_suffix(".pdf")
    out.write_bytes(resp.content)
    print(f"PAdES seal → {out}")

Verify a CAdES seal with curl

# Verify a CAdES seal — pass original file + .p7s
# The server checks the signature and returns structured JSON

curl -s -X POST https://api.sigill.ai/seal/verify \
  -F "file=@report.json" \
  -F "p7s=@report.p7s" \
  | jq '{
      format: .format,
      intact: .cades.hashMatch,
      signer: .cades.certificate.subject,
      timestamp: .cades.timestamp.genTime,
      tsa: .cades.timestamp.tsaName,
      qualified: .cades.timestamp.qc.isEidasQualified
    }'

# To also verify the embedded timestamp against the standalone .tsr:
curl -s -X POST https://api.sigill.ai/seal/verify \
  -F "file=@report.json" \
  -F "p7s=@report.p7s" \
  -F "tsr=@report.tsr" \
  | jq '.cades.tsrSource'  # → "external" when .tsr matched