Public API · Signing requests · CodeB Wallet

Signing requests API.

Everything behind sign-request.html: upload PDFs, sign them with CodeB Wallet by scanning a QR code, and invite other people by e-mail to sign one after another. The wallet endpoints are capability URLs (128-bit step ids, rate-limited); the requester endpoints need the site’s sign-in.

Honest scope. Advanced electronic signatures with any wallet signature source; “qualified” only when the signer’s signature card states it in its certificate. Designed to follow ETSI TS 119 432 and ARF 3.0 topic 16; not certified. Walkthrough: Sign a PDF with CodeB Wallet.

How a signing request works

  1. A signed-in user creates a request on sign-request.html (or with api=create): 1–10 PDFs, the level (advanced, or qualified = a signature card whose certificate states a qualified signature), optionally “I sign first” and up to 5 invited people (guests: 2) in signing order.
  2. Whose turn it is gets a wallet link eudi-rqes://?client_id=x509_san_dns:<host>&request_uri=<host>/sign-request.ashx?r=<sid>: the requester as a QR code on the page, an invited person by e-mail.
  3. CodeB Wallet fetches the signed request object (?r=), checks it against the pinned CA (?ca), shows who asks and every document (?doc=), signs with the person's signature card (NFC) or the CodeB signing service, and posts the signed PDFs to ?respond= — or declines.
  4. The server checks each signed PDF: an incremental update of exactly the input, one verifying CMS signature, no changes that could hide or replace content; the PAdES level is reported. The next signer is then mailed; when everyone has signed, everyone gets a link to the signed PDF (?final=).

Wallet side designed to follow the OpenID4VP-based signing request of ETSI TS 119 432 and ARF 3.0 topic 16; not certified. “Qualified” is shown only when the signer's certificate states it; there is no trusted-list check. Your own turn stays open 24 hours, an invited person's 72 hours; requests are kept 30 days after they close. Each account sees only its own requests.

Wallet and invited people (capability URLs)

GET /sign-request.ashx?ca Open #

The relying-party CA certificate the wallet pins (PEM).

Responses

  • 200 — PEM

GET /sign-request.ashx?r={sid} Open #

Signed request object of the active step (OpenID4VP, ETSI TS 119 432). Fresh ES256 JWT, typ oauth-authz-req+jwt, exp 5 minutes, x5c chain to the CA from ?ca. Claims: client_id x509_san_dns:<host>, response_mode direct_post, response_uri, state, nonce, signatureQualifier (eu_eidas_qes / eu_eidas_aes), documentDigests (label + SHA-256 per document), documentLocations (?doc URLs). The wallet link is eudi-rqes://?client_id=...&request_uri=<this URL>.

Parameters

  • sid (path, required) — string

Responses

  • 200 — JWT
  • 404 — Unknown step
  • 409 — not_yet: earlier signers have not signed
  • 410 — request_closed
  • 429 — Rate limit

GET /sign-request.ashx?doc={sid}.{n}.{token} Open #

Input PDF n of the active step (original or the previous signer's PDF).

Parameters

  • sid (path, required) — string
  • n (path, required) — integer
  • token (path, required) — string

Responses

  • 200 — PDF
  • 404 — Unknown
  • 410 — Closed
  • 429 — Rate limit

POST /sign-request.ashx?respond={sid} Open #

Wallet answer (direct_post): signed PDFs or a decline. Each signed PDF must be an incremental update of the input, byte for byte, with one verifying CMS signature; changes that could hide or replace content are refused; the PAdES level is reported. Idempotent for the same state and body. When an invited signer's answer is accepted, a guest account's signatures for it do not count toward its daily limit.

Parameters

  • sid (path, required) — string

Request (application/x-www-form-urlencoded)

{
  "state": "...",
  "documentWithSignature": "...",
  "error": "user_cancelled"
}

Responses

  • 200 — Accepted
  • 400 — invalid_request or invalid_signed_document (with the reason)
  • 404 — Unknown
  • 410 — request_closed

200 example

{}

GET /sign-request.ashx?invite={sid} Open #

Invited person's page data (documents, who asks, own turn, wallet link, downloads).

Parameters

  • sid (path, required) — string

Responses

  • 200 — Page data
  • 404 — Unknown

200 example

{}

GET /sign-request.ashx?final={sid}.{n}.{token} Open #

Signed PDF for an invited signer, once everyone has signed.

Parameters

  • sid (path, required) — string
  • n (path, required) — integer
  • token (path, required) — string

Responses

  • 200 — PDF
  • 404 — Unknown or not yet signed

GET /sign-request.ashx?verify={sid}.{n}.{token} Open #

Signature report of the final signed PDF (invited signer). Same token as final=. Every signature in the PDF is checked: integrity of the signed revision, later changes, certificate and timestamp. No check against the EU trusted lists.

Parameters

  • sid (path, required) — string
  • n (path, required) — integer
  • token (path, required) — string

Responses

  • 200 — Report
  • 404 — Unknown or not yet signed
  • 503 — busy (two reports at a time)

200 example

{
  "status": "valid",
  "summary": "...",
  "size": 0,
  "trust_note": "...",
  "signatures": [
    {
      "index": 0,
      "revision_end": 0,
      "covers_whole_file": true,
      "subfilter": "...",
      "kind": "signature",
      "status": "valid",
      "integrity": true,
      "later_changes": "none",
      "later_changes_detail": "...",
      "signer": "..."
    }
  ]
}

Requester API (signed-in user)

Bearer access token of this site’s own sign-in (client codeb-admin). Any account, guests included.

POST /sign-request.ashx?api=create Bearer #

Create a signing request. 1-10 PDFs (40 MB each, 50 MB together), signature level qes (signature card whose certificate states a qualified signature) or aes, optional note, self=1 to sign first, invited e-mails in signing order (up to 5; guests 2; 20 invitations a day). Your own turn stays open 24 h, an invited person's 72 h; requests are kept 30 days after they close. The first invited signer is mailed at once when the requester does not sign first.

Request (multipart/form-data)

{
  "file": [
    "(the file itself)"
  ],
  "label": [
    "..."
  ],
  "level": "qes",
  "note": "...",
  "self": "0",
  "invite": [
    "person@example.com"
  ]
}

Responses

  • 200 — Created
  • 400 — invalid_request (with the reason)
  • 401 — Sign-in required
  • 409 — limit
  • 429 — slow_down

200 example

{
  "id": "...",
  "status": "open",
  "level": "qes",
  "note": "...",
  "created": "2026-10-16T10:00:00Z",
  "expires": "2026-10-16T10:00:00Z",
  "closed_at": "...",
  "keep_until": "...",
  "current": 0,
  "my_turn": true
}

GET /sign-request.ashx?api=list Bearer #

The signed-in user's own signing requests. Each account sees only its own requests.

Responses

  • 200 — List
  • 401 — Sign-in required

200 example

{
  "requests": [
    {
      "id": "...",
      "status": "open",
      "level": "qes",
      "note": "...",
      "created": "2026-10-16T10:00:00Z",
      "expires": "2026-10-16T10:00:00Z",
      "closed_at": "...",
      "keep_until": "...",
      "current": 0,
      "my_turn": true
    }
  ]
}

GET /sign-request.ashx?api=status Bearer #

One request (polled by the page).

Parameters

  • id (query, required) — string

Responses

  • 200 — Request
  • 401 — Sign-in required
  • 404 — Not found or another account's

200 example

{
  "id": "...",
  "status": "open",
  "level": "qes",
  "note": "...",
  "created": "2026-10-16T10:00:00Z",
  "expires": "2026-10-16T10:00:00Z",
  "closed_at": "...",
  "keep_until": "...",
  "current": 0,
  "my_turn": true
}

POST /sign-request.ashx?api=invite Bearer #

Add invited signers at the end of an open request.

Request (application/json)

{
  "id": "...",
  "emails": [
    "person@example.com"
  ]
}

Responses

  • 200 — Request
  • 400 — invalid_request
  • 404 — not_found
  • 409 — not_open, already_invited or limit
  • 429 — slow_down

200 example

{
  "id": "...",
  "status": "open",
  "level": "qes",
  "note": "...",
  "created": "2026-10-16T10:00:00Z",
  "expires": "2026-10-16T10:00:00Z",
  "closed_at": "...",
  "keep_until": "...",
  "current": 0,
  "my_turn": true
}

POST /sign-request.ashx?api=resend Bearer #

Send the invitation e-mail again to the invited person whose turn it is. Owner only, open request; at most 3 times per signer, not twice within a minute; counts as an invitation for the daily limit. The summary's mail field says whether the mail was handed to the mail server.

Request (application/json)

{
  "id": "..."
}

Responses

  • 200 — Request
  • 404 — not_found
  • 409 — not_open, not_waiting, resend_limit or limit
  • 429 — slow_down
  • 502 — mail_failed: could not be handed to the mail server

200 example

{
  "id": "...",
  "status": "open",
  "level": "qes",
  "note": "...",
  "created": "2026-10-16T10:00:00Z",
  "expires": "2026-10-16T10:00:00Z",
  "closed_at": "...",
  "keep_until": "...",
  "current": 0,
  "my_turn": true
}

POST /sign-request.ashx?api=withdraw Bearer #

Withdraw an open request (the invited person whose turn it was is told).

Request (application/json)

{
  "id": "..."
}

Responses

  • 200 — Done
  • 404 — not_found
  • 409 — not_open

200 example

{}

POST /sign-request.ashx?api=delete Bearer #

Delete a request and its documents.

Request (application/json)

{
  "id": "..."
}

Responses

  • 200 — Done
  • 404 — not_found
  • 409 — not_open

200 example

{}

GET /sign-request.ashx?api=download Bearer #

Download an original or the (latest) signed PDF of an own request.

Parameters

  • id (query, required) — string
  • n (query, required) — integer
  • kind (query, required) — string

Responses

  • 200 — PDF
  • 400 — invalid_request
  • 404 — Not found or no longer kept

GET /sign-request.ashx?api=verify Bearer #

Signature report of an original or signed PDF of an own request. 30 per minute per account.

Parameters

  • id (query, required) — string
  • n (query, required) — integer
  • kind (query, required) — string

Responses

  • 200 — Report
  • 400 — invalid_request
  • 401 — Not signed in
  • 404 — Not found or no longer kept
  • 429 — slow_down
  • 503 — busy

200 example

{
  "status": "valid",
  "summary": "...",
  "size": 0,
  "trust_note": "...",
  "signatures": [
    {
      "index": 0,
      "revision_end": 0,
      "covers_whole_file": true,
      "subfilter": "...",
      "kind": "signature",
      "status": "valid",
      "integrity": true,
      "later_changes": "none",
      "later_changes_detail": "...",
      "signer": "..."
    }
  ]
}

POST /sign-request.ashx?api=verify-upload Bearer #

Signature report of any PDF (not stored). The body is the PDF itself (at most 40 MB). Nothing is stored. 10 per minute per account. The page sends a local file only when it contains signatures and the user asks for the check.

Request (application/pdf)

"(the file itself)"

Responses

  • 200 — Report
  • 400 — invalid_request (not a PDF)
  • 401 — Not signed in
  • 413 — Larger than 40 MB
  • 429 — slow_down
  • 503 — busy

200 example

{
  "status": "valid",
  "summary": "...",
  "size": 0,
  "trust_note": "...",
  "signatures": [
    {
      "index": 0,
      "revision_end": 0,
      "covers_whole_file": true,
      "subfilter": "...",
      "kind": "signature",
      "status": "valid",
      "integrity": true,
      "later_changes": "none",
      "later_changes_detail": "...",
      "signer": "..."
    }
  ]
}

Frequently asked questions

How do I ask several people to sign a PDF with CodeB Wallet?

Open sign-request.html, sign in, upload the PDF, choose whether you sign first and enter the e-mail addresses of the people who should sign, in order. Each person gets an e-mail when it is their turn and signs in CodeB Wallet; at the end everyone gets a link to the signed PDF.

Which signatures can be used?

Advanced: any signature source in CodeB Wallet, the person's signature card or the CodeB signing service. Qualified: only a signature card whose certificate states a qualified signature. CodeB shows 'qualified' only as stated in the certificate; it does not check the EU trusted lists.

What does the server check on a signed PDF?

That it is an incremental update of exactly the PDF the signer received, with one CMS signature that verifies and a certificate valid at the signing time, and that no change could hide or replace content. The PAdES level (B-B, B-T, B-LT, B-LTA) is reported.

Did my invitation e-mail go out?

The request list shows for the invited person whose turn it is whether the e-mail was handed to the mail server or failed, and a 'Send e-mail again' button (at most 3 times, once a minute).

Can I see and check the signatures in a PDF?

Yes. The viewer on sign-request.html lists every signature on the right, like a desktop PDF reader: whether the signed part is unchanged, whether anything was changed afterwards, who signed, the certificate and any timestamp. The same report is available from the API (api=verify, api=verify-upload, verify=). Certificates are not checked against the EU trusted lists.

Do the signatures get a time stamp?

Yes, when a time-stamp server answers: CodeB Wallet asks the CodeB time-stamp service (/rqes/csc/v2/signatures/timestamp, then /rqes/csc/v2/signatures/tsa as fallback) and embeds the token, so the signature is PAdES-B-T. If neither answers, the PDF is signed without a time stamp (PAdES-B-B). The signature report shows which one a signature has.

Do invited signatures count against a guest account's daily limit?

No. A guest account can sign 5 documents per UTC day with the CodeB signing service; signatures given as an invited signer in someone else's request are given back once the request accepts the signed PDF.

Machine-readable: /openapi.json (OpenAPI 3.1, also at /.well-known/openapi.json) · All public APIs · Questions