> ## Documentation Index
> Fetch the complete documentation index at: https://docs.signsealship.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Proof Passport API: seal executed PDFs with KMS

> Seal an executed PDF with Google Cloud KMS, gather RFC 3161 and OpenTimestamps evidence, and get a public verification URL — the Proof Passport API.

Your platform submits a finished, signed PDF. It comes back sealed with Google Cloud KMS, timestamped by independent authorities, and wrapped in a public verification URL — the same trustworthy artifact SignSealShip's own notarize-and-ship orders produce. One endpoint, real evidence, nothing you can't independently check.

## What a passport holds

* **Tamper-evident seal** — an asymmetric signature whose private key never leaves Google Cloud KMS: a CMS/PKCS#7 signature visible in any PDF reader's signature panel, with `/ByteRange` tamper-evidence. Change one byte after sealing and every verifier can tell.
* **Independent time** — a trusted-timestamp token from an independent RFC 3161 time-stamping authority over the sealed document's SHA-256. An archivable attestation that the sealed bytes existed at that moment — not SignSealShip's word for it.
* **Bitcoin anchor** — the sealed hash is submitted to public OpenTimestamps calendars, producing a detached proof anyone can verify or upgrade with standard tooling. It is honestly reported as `pending` until a Bitcoin block confirms it — never a claim that hasn't been earned.
* **Chain of custody** — a 128-bit random verification code plus a custody record: the pre-seal document hash, the post-seal hash, and when it was sealed. Possession of the code (or the sealed file) is the authorization to see it.
* **Public verification** — a URL anyone can check without a SignSealShip account and without trusting you. The artifact proves itself.

## What it is not

* **A signing tool** — you bring the already-executed document; SignSealShip seals and proves it.
* **A legal opinion** — the evidence is cryptographic fact, not advice on your document's legal sufficiency.
* **A place your customers' data lives** — passport rows hold hashes and a verify code, never document content or PII. The upload is never retained beyond producing the sealed artifact.

## Seal a document

`POST /api/passport/seal`

Requires your partner key. Send the executed PDF as a multipart form part named `file`, up to 35 MB. The bytes are checked for PDF magic bytes, streamed into memory (never to disk), and discarded after sealing. Rate limited at 12 requests per minute per IP.

<ParamField body="file" type="file" required>
  The executed PDF, as the multipart part named `file`. Maximum 35 MB.
</ParamField>

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://signsealship.com/api/passport/seal \
    -H "Authorization: Bearer sss_pk_your_key" \
    -F "file=@executed.pdf"
  ```

  ```javascript Node theme={null}
  import { readFile } from "node:fs/promises";

  const form = new FormData();
  form.append(
    "file",
    new Blob([await readFile("executed.pdf")], { type: "application/pdf" }),
    "executed.pdf",
  );

  const res = await fetch("https://signsealship.com/api/passport/seal", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SSS_PARTNER_KEY}` },
    body: form,
  });
  const passport = await res.json();
  console.log(passport.verifyUrl);
  ```

  ```python Python theme={null}
  import os
  import requests

  with open("executed.pdf", "rb") as f:
      res = requests.post(
          "https://signsealship.com/api/passport/seal",
          headers={"Authorization": f"Bearer {os.environ['SSS_PARTNER_KEY']}"},
          files={"file": ("executed.pdf", f, "application/pdf")},
      )
  passport = res.json()
  print(passport["verifyUrl"])
  ```
</CodeGroup>

<ResponseField name="passportId" type="string">
  The passport's id (UUID). Use it with `GET /api/passport/{id}`.
</ResponseField>

<ResponseField name="verifyCode" type="string">
  The 128-bit public verification capability code.
</ResponseField>

<ResponseField name="verifyUrl" type="string">
  Absolute public verification URL,
  `https://signsealship.com/api/passport/verify/{verifyCode}`.
</ResponseField>

<ResponseField name="docSha256" type="string">
  SHA-256 (hex) of the exact bytes you uploaded — the pre-seal hash.
</ResponseField>

<ResponseField name="sealedSha256" type="string">
  SHA-256 (hex) of the sealed PDF SignSealShip produced and stored.
</ResponseField>

<ResponseField name="status" type="string">
  `"sealed"` once the KMS seal is applied and stored.
</ResponseField>

<ResponseField name="timestamps" type="object">
  Independent timestamp evidence over the sealed hash. Either member is `null`
  when that authority could not be reached — the response never fakes
  evidence it didn't gather.

  <Expandable title="timestamps">
    <ResponseField name="rfc3161" type="object">
      `authority` — the time-stamping authority URL used; `timestampedAtUtc` —
      the authority's attested time. `null` when no token was obtained.
    </ResponseField>

    <ResponseField name="openTimestamps" type="object">
      `calendar` — the public calendar the hash was submitted to;
      `submittedAtUtc` — submission time; `status` — `"pending"` until a
      Bitcoin block confirms the proof. `null` when submission failed.
    </ResponseField>
  </Expandable>
</ResponseField>

```json 200 OK theme={null}
{
  "passportId": "0d5e2f6a-1b3c-4d7e-8f90-a1b2c3d4e5f6",
  "verifyCode": "abcdefghijklmnopqrstuvwxyz",
  "verifyUrl": "https://signsealship.com/api/passport/verify/abcdefghijklmnopqrstuvwxyz",
  "docSha256": "5f6e...1a2b",
  "sealedSha256": "8c9d...3e4f",
  "status": "sealed",
  "timestamps": {
    "rfc3161": {
      "authority": "https://timestamp.example",
      "timestampedAtUtc": "2026-07-12T18:30:12+00:00"
    },
    "openTimestamps": {
      "calendar": "https://calendar.example",
      "submittedAtUtc": "2026-07-12T18:30:13+00:00",
      "status": "pending"
    }
  }
}
```

Errors: `400` `{"error": "Expected a multipart 'file' PDF part up to 35MB."}` for a missing, oversized, or non-multipart upload, `400` `{"error": "Uploaded bytes are not a PDF."}` when the magic bytes don't match, `401` for a bad key.

A signed `passport.sealed` webhook fires on sealing if you have registered one — see [webhooks](/api-reference/webhooks).

## Fetch a passport

`GET /api/passport/{id}`

Requires your partner key. Partner isolation is enforced: a passport is visible only to the partner that created it.

<ParamField path="id" type="string" required>
  The passport's UUID from the seal response.
</ParamField>

```bash curl theme={null}
curl https://signsealship.com/api/passport/{id} \
  -H "Authorization: Bearer sss_pk_your_key"
```

Returns the same fields as the seal response minus `timestamps`, plus `createdAt`:

```json 200 OK theme={null}
{
  "passportId": "0d5e2f6a-1b3c-4d7e-8f90-a1b2c3d4e5f6",
  "verifyCode": "abcdefghijklmnopqrstuvwxyz",
  "verifyUrl": "https://signsealship.com/api/passport/verify/abcdefghijklmnopqrstuvwxyz",
  "docSha256": "5f6e...1a2b",
  "sealedSha256": "8c9d...3e4f",
  "status": "sealed",
  "createdAt": "2026-07-12T18:30:11+00:00"
}
```

Unknown ids — including another partner's — return `404` `{"error": "No such passport."}`.

## Verify a passport (public)

`GET /api/passport/verify/{code}`

Public and self-contained: possession of the code is the authorization. Never exposes the partner identity or any other passport. Rate limited at 12 requests per minute per IP. Malformed and unknown codes return the identical generic `{"verdict": "unknown"}` 404.

<ParamField path="code" type="string" required>
  The 26-character verify code.
</ParamField>

```bash curl theme={null}
curl https://signsealship.com/api/passport/verify/{code}
```

<ResponseField name="verdict" type="string">
  `"verified"` when the code resolves to a sealed passport.
</ResponseField>

<ResponseField name="verifyCode" type="string">
  Echo of the code.
</ResponseField>

<ResponseField name="sealedSha256" type="string">
  SHA-256 (hex) of the sealed PDF — compare against a file in hand.
</ResponseField>

<ResponseField name="docSha256" type="string">
  SHA-256 (hex) of the original uploaded bytes.
</ResponseField>

<ResponseField name="sealedAtUtc" type="string">
  When the passport was sealed.
</ResponseField>

<ResponseField name="source" type="string">
  `"proof_passport_api"` — this record came through the partner API.
</ResponseField>

<ResponseField name="certificateAvailable" type="boolean">
  Whether the sealed artifact is archived and downloadable below.
</ResponseField>

## Download the sealed document (public)

`GET /api/passport/verify/{code}/document`

Streams the byte-identical sealed PDF as `application/pdf`. Any holder of the code can fetch it; the file also verifies on its own in any PDF reader's signature panel — no SignSealShip involvement required. Unknown codes and missing artifacts return the generic `{"verdict": "unknown"}` 404.

```bash curl theme={null}
curl -L -o sealed.pdf \
  https://signsealship.com/api/passport/verify/{code}/document
```

## Register a passport webhook

`POST /api/passport/webhooks`

Requires your partner key. Registers an https endpoint for `passport.sealed` events; the signing secret is returned exactly once — only its hash is stored. Full payload and signature verification details are on the [webhooks page](/api-reference/webhooks).

<ParamField body="url" type="string" required>
  Your https webhook endpoint.
</ParamField>

```bash curl theme={null}
curl -X POST https://signsealship.com/api/passport/webhooks \
  -H "Authorization: Bearer sss_pk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com/hooks/signsealship" }'
```

```json 200 OK theme={null}
{
  "webhookId": "7a1b2c3d-4e5f-6071-8293-a4b5c6d7e8f9",
  "url": "https://example.com/hooks/signsealship",
  "signingSecret": "sss_whsec_shown_exactly_once",
  "signatureHeader": "SignSealShip-Signature",
  "verification": "HMAC-SHA256 over \"{t}.{rawBody}\" using key = lowercase_hex(sha256(signingSecret)); compare against the v1= value and reject timestamps older than 5 minutes."
}
```

<Warning>
  Store `signingSecret` immediately — it is never shown again. SignSealShip
  keeps only its SHA-256 hash.
</Warning>
