{"openapi": "3.1.0", "info": {"title": "Tvarka Sign API", "version": "1.2.1", "x-status": "Live and publicly reachable at https://sign-api.tvarka.pro since 2026-08-27, on both production boxes and behind Cloudflare. The first production tenant was onboarded 2026-08-28 and the launch gate ceremony ran the same day: a production signing ordered through this API was signed with a real eID (Mobile-ID) and validated QUALIFIED_VALID, and webhook delivery was exercised the same day against an external production receiver with the HMAC signature and idempotency key verified on arrival.", "description": "Machine channel for Tvarka Sign: post a document and a list of signers,\nand Tvarka runs its ordinary qualified signing ceremony for each of them.\n\n**What this API is.** Orchestration. The ceremony itself is the shipped\nTvarka Sign ceremony a person already uses: the same hosted page, the\nsame signing methods (Lithuanian ID card over NFC in the Tvarka Sign\nmobile app, Smart-ID and Mobile-ID on the hosted page), the same PIN\nrules, the same invitation lifecycle. Nothing\nhere signs anything - a signature is always a human act with their own\neID, so every endpoint below is about getting a document in front of the\nright person and finding out what they did.\n\n**Signature formats.** This orchestration API accepts PDF, appendable\nASiC-E containers, and the ADOC family - `.adoc`, `.bedoc`, `.cedoc`,\n`.gedoc`, `.ggedoc` - for countersigning one that is already signed.\nA container is signed in place, never converted. MDOC is not accepted\nfor countersigning: its composer writes a first signature only.\nThe complete Tvarka API estate produces every\nRegulation (EU) 2026/248 Annex I family: PAdES, XAdES, CAdES, JAdES and\nASiC. The ATK API is the lower-level card and reader API, including full\nLT ATK NFC, USB/reader and detached CAdES support. The DD API produces\nJAdES. See the format matrix in the developer guide before selecting a\nsurface.\n\n**Deliberate scope.** Tvarka offers simple, advisory signature\nvalidation. It does not sell qualified validation and it does not offer\nan electronic-seal API. Those are product boundaries, not launch gaps.\n\n**Tenancy and billing.** A tenant is a Tvarka entity with a Sign\nworkspace, and ceremonies are raised in that entity's name, so the\ncounterparty sees who is actually asking. Signatures are billed to that\nworkspace on the ordinary per-signature price ladder; there is no\nseparate API tariff and no subscription.\n\n**Sandbox.** A `tsk_test_` key opens real ceremonies but is exempt from\nthe funding precondition, and can drive a signing to `completed`,\n`declined` or `expired` through `/simulate`. A simulated completion is\nexplicitly marked `sandbox_simulation` and returns a real downloadable\nartifact, so download, hashing and storage can be exercised without a\nreal eID. The sandbox never adds a signature: an unsigned PDF comes back\nas the caller's own pages with a banner saying it is not signed, and\nanything posted already signed comes back byte-identical. Every artifact\nis flagged `sandbox: true`. Tvarka issues no test signatures. A\nproduction completion is always a real human ceremony.\n", "contact": {"name": "Tvarka", "email": "info@tvarka.pro", "url": "https://tvarka.pro"}}, "servers": [{"url": "https://sign-api.tvarka.pro", "description": "Production and sandbox (selected by the key)"}], "security": [{"bearerAuth": []}], "tags": [{"name": "Signings", "description": "Create and follow a signing."}], "paths": {"/v1/signings": {"get": {"tags": ["Signings"], "operationId": "listSignings", "summary": "List signings", "parameters": [{"name": "status", "in": "query", "schema": {"$ref": "#/components/schemas/SigningStatus"}}, {"name": "limit", "in": "query", "schema": {"type": "integer", "minimum": 1, "maximum": 100, "default": 50}}, {"name": "startingAfter", "in": "query", "schema": {"type": "string", "format": "uuid"}}], "responses": {"429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "A newest-first page of signings.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SigningList"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "401": {"$ref": "#/components/responses/Problem"}}}, "post": {"tags": ["Signings"], "operationId": "createSigning", "summary": "Create a signing", "description": "Stores the document, opens one ceremony per signer, and returns the\nceremony URLs (or emails them, with `delivery: email`).\n\nAll signers sign the same document and are unordered: whoever opens\ntheir link first signs first, and each subsequent signer signs the\noutput the previous one produced. Two signers never sign at the same\nmoment - the ceremony serializes them.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/CreateSigningRequest"}}}}, "responses": {"201": {"description": "The signing was created and every signer has a live ceremony.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signing"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "401": {"$ref": "#/components/responses/Problem"}, "402": {"description": "The tenant cannot pay for the ceremony: no payment rail, an\noverdue invoice, or a tripped usage ceiling. Nothing was\ncreated and nobody was invited. `recoveryUrl` names the web\npage where the tenant can fix it.\n", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "403": {"$ref": "#/components/responses/Problem"}, "422": {"description": "A well-formed request for a container that cannot take another\nsignature - unsigned, already finalized, or structurally\nunsound. Not billed.\n", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "429": {"$ref": "#/components/responses/Problem"}, "503": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}": {"get": {"tags": ["Signings"], "operationId": "getSigning", "summary": "Get a signing", "description": "Current state of the signing and each of its signers.", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The signing.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signing"}}}}, "404": {"$ref": "#/components/responses/Problem"}}}, "patch": {"tags": ["Signings"], "operationId": "updateSigning", "summary": "Update a live signing deadline", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"expiresInDays": {"type": "integer", "minimum": 1, "maximum": 30}}, "required": ["expiresInDays"], "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The updated signing.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signing"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}}}, "delete": {"tags": ["Signings"], "operationId": "eraseSigning", "summary": "Erase one signing", "description": "Cancels a live signing first, then purges captured identity and\ncaller-supplied personal data. For a document uploaded through this\nAPI, its bytes and vault record are also deleted. If the MCP server\nopened the ceremony on a pre-existing vault document, that shared\ndocument and its bytes are preserved while the dedicated ceremony is\nremoved. A de-identified audit row remains. Idempotent.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "Erasure result.", "content": {"application/json": {"schema": {"type": "object", "properties": {"signingId": {"type": "string", "format": "uuid"}, "deleted": {"type": "boolean", "const": true}, "changed": {"type": "boolean"}}, "required": ["signingId", "deleted", "changed"], "additionalProperties": false}}}}, "404": {"$ref": "#/components/responses/Problem"}, "503": {"$ref": "#/components/responses/Problem"}}}}, "/v1/batches": {"post": {"tags": ["Batches"], "operationId": "createBatch", "summary": "Raise one signing per document for one set of parties", "description": "A batch is a **grouping, not a single signing act**. Every document\nkeeps its own ceremony and its own signature, because that is what\nhappens: the Lithuanian ID card produces one signature per document,\nand the external ceremony has no USB door where the product's batch\nsigning lives. This buys one call instead of N and one identifier to\npoll. It does not buy one PIN for many documents, and nothing here\nshould be read as promising that.\n\nCreation is atomic: either every signing in the batch is raised, or\nthe error response means none was.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"title": {"type": "string", "maxLength": 255}, "documents": {"type": "array", "minItems": 1, "maxItems": 20, "items": {"type": "object", "properties": {"name": {"type": "string"}, "content": {"type": "string", "format": "byte"}, "fileToken": {"type": "string", "format": "uuid"}, "flatten": {"type": "boolean"}}, "required": ["name"], "additionalProperties": false}}, "signers": {"type": "array", "minItems": 1, "items": {"$ref": "#/components/schemas/SignerRequest"}}, "delivery": {"type": "string", "enum": ["link", "email"]}, "signingOrder": {"type": "string", "enum": ["parallel", "sequential"]}, "webhookUrl": {"type": "string", "format": "uri"}, "externalId": {"type": "string", "maxLength": 120}, "expiresInDays": {"type": "integer", "minimum": 1}, "softDeadlineInDays": {"type": "integer", "minimum": 1, "description": "Applied to every signing the batch raises."}}, "required": ["title", "documents", "signers"], "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "201": {"description": "The batch and every signing it raised.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Batch"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "402": {"$ref": "#/components/responses/Problem"}}}}, "/v1/batches/{batchId}": {"get": {"tags": ["Batches"], "operationId": "getBatch", "summary": "Poll a batch and every signing in it", "parameters": [{"$ref": "#/components/parameters/BatchId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The batch.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Batch"}}}}, "404": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}/archive": {"post": {"tags": ["Signings"], "operationId": "archiveSigning", "summary": "Raise the signed output to a long-term level", "description": "PDF goes PAdES-B-T to B-LT, ASiC-E goes XAdES-T to XAdES-LT: the\ncertificate chain and fresh revocation evidence are embedded so the\nsignature can still be validated once the signing certificate has\nexpired.\n\nADOC is refused by name. ADOC-V1.0 specifies XAdES-T and its\nvalidators expect that level, so \"upgrading\" one would be a\nconformance risk dressed as a favour.\n\nAlready-archived output comes back unchanged with `upgraded: false`,\nso calling this twice costs a parse and not a second signature. The\npre-archive version is kept - an upgrade adds a version, never\nreplaces one.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The level now held, and whether this call raised it.", "content": {"application/json": {"schema": {"type": "object", "properties": {"signingId": {"type": "string", "format": "uuid"}, "level": {"type": "string", "example": "PAdES-B-LT"}, "upgraded": {"type": "boolean"}, "document": {"$ref": "#/components/schemas/SignedDocument"}}}}}}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}, "422": {"$ref": "#/components/responses/Problem"}}}}, "/v1/files": {"post": {"tags": ["Files"], "operationId": "uploadFile", "summary": "Store a document once for reuse across signings", "description": "Returns a `fileToken` a later signing may reference instead of\ncarrying base64 again. The same intake as a posted document, so a\nfile that would be refused inline is refused here rather than at the\nsigning that uses it.\n\nThe bytes are **copied** into each signing that uses the token, never\nshared by reference: deleting a file, or letting it expire, can never\nreach backwards into a signing that already used it.\n", "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "content": {"type": "string", "format": "byte"}}, "required": ["name", "content"], "additionalProperties": false}}}}, "responses": {"429": {"$ref": "#/components/responses/Problem"}, "201": {"description": "The stored file.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StoredFile"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "401": {"$ref": "#/components/responses/Problem"}}}}, "/v1/files/{fileToken}": {"get": {"tags": ["Files"], "operationId": "getFile", "summary": "Inspect a stored file", "parameters": [{"$ref": "#/components/parameters/FileToken"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The stored file.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StoredFile"}}}}, "404": {"$ref": "#/components/responses/Problem"}}}, "delete": {"tags": ["Files"], "operationId": "deleteFile", "summary": "Purge a stored file", "description": "Drops the bytes. Signings that already referenced the token are\nuntouched, because they hold their own copy.\n", "parameters": [{"$ref": "#/components/parameters/FileToken"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The file, now purged.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/StoredFile"}}}}, "404": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}/comments": {"get": {"tags": ["Signings"], "operationId": "listComments", "summary": "Read the notes on a signing", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The comment thread, oldest first.", "content": {"application/json": {"schema": {"type": "object", "properties": {"signingId": {"type": "string", "format": "uuid"}, "comments": {"type": "array", "items": {"$ref": "#/components/schemas/Comment"}}}}}}}, "404": {"$ref": "#/components/responses/Problem"}}}, "post": {"tags": ["Signings"], "operationId": "addComment", "summary": "Add a note to a signing", "description": "Your own thread, for your own audit trail. Comments are **not shown\nto signers**: the ceremony's copy is platform-controlled, so a\nsigner-visible comment would need a surface that does not exist, and\none written in the belief that a signer will read it is worse than\nnone.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"body": {"type": "string", "maxLength": 4000}, "authorLabel": {"type": "string", "maxLength": 120, "description": "Your own name for whoever wrote it. Echoed back."}}, "required": ["body"], "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "201": {"description": "The stored comment.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Comment"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "404": {"$ref": "#/components/responses/Problem"}}}}, "/v1/erasure": {"post": {"tags": ["Signings"], "operationId": "eraseSignings", "summary": "Erase terminal signings in bulk", "description": "Purges the client's terminal signing records and any order-owned\nuploads. Pre-existing vault documents used through MCP are preserved.\nIn-flight signings are left untouched; erase one directly if\ncancellation is intended.\n", "requestBody": {"required": false, "content": {"application/json": {"schema": {"type": "object", "properties": {"before": {"type": "string", "format": "date-time"}}, "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "Number purged and number that must be retried.", "content": {"application/json": {"schema": {"type": "object", "properties": {"purged": {"type": "integer"}, "failed": {"type": "integer"}}, "required": ["purged", "failed"], "additionalProperties": false}}}}, "400": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}/cancel": {"post": {"tags": ["Signings"], "operationId": "cancelSigning", "summary": "Cancel a signing", "description": "Retracts every invitation that has not been used: the ceremony link\ndies immediately, and the identity data the ceremony had captured\nabout those signers is erased.\n\nSignatures already collected are untouched. A qualified signature\nexists once it is made, and this endpoint does not pretend otherwise\n- the signed document remains downloadable.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The signing is cancelled.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signing"}}}}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"description": "The signing had already ended.", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}, "/v1/signings/{signingId}/document": {"get": {"tags": ["Signings"], "operationId": "downloadSignedDocument", "summary": "Download the signed document", "description": "The newest signed output. Available as soon as one signer has\nsigned, so a partially signed document can be fetched; the\n`X-Tvarka-Document-Sha256` header carries its digest. On a sandbox\nsimulation this streams the unsigned artifact instead, marked by an\n`X-Tvarka-Sandbox` header.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The signed document.", "headers": {"X-Tvarka-Document-Sha256": {"schema": {"type": "string"}, "description": "SHA-256 of the returned bytes."}, "X-Tvarka-Sandbox": {"schema": {"type": "string"}, "description": "Present only on a sandbox artifact\n(`simulated-artifact-no-signature`). The bytes carry no\nsignature.\n"}}, "content": {"application/octet-stream": {"schema": {"type": "string", "format": "binary"}}}}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"description": "Nobody has signed yet.", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}, "/v1/signings/{signingId}/signers": {"post": {"tags": ["Signings"], "operationId": "addSigner", "summary": "Add a signer to a live signing", "description": "On a `sequential` signing the newcomer joins the END of the queue:\nthey come back `waiting`, their link stays dead until their turn,\nand no delivery goes out before it. Only a parallel signing hands\nback a live ceremony URL immediately.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"$ref": "#/components/schemas/SignerRequest"}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "201": {"description": "The signer and their ceremony.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signer"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "402": {"$ref": "#/components/responses/Problem"}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}/signers/{signerId}": {"patch": {"tags": ["Signings"], "operationId": "updateSignerMethods", "summary": "Update a pending signer's allowed methods", "parameters": [{"$ref": "#/components/parameters/SigningId"}, {"$ref": "#/components/parameters/SignerId"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"methods": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/components/schemas/SigningMethod"}}}, "required": ["methods"], "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The updated signer.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signer"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}}}, "delete": {"tags": ["Signings"], "operationId": "removeSigner", "summary": "Remove a pending signer", "description": "Retracts the ceremony link before removing the signer.", "parameters": [{"$ref": "#/components/parameters/SigningId"}, {"$ref": "#/components/parameters/SignerId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "204": {"description": "The signer was removed."}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}}}}, "/v1/signings/{signingId}/signers/{signerId}/remind": {"post": {"tags": ["Signings"], "operationId": "remindSigner", "summary": "Remind one signer", "description": "Re-sends the invitation email. One reminder per signer per hour.", "parameters": [{"$ref": "#/components/parameters/SigningId"}, {"$ref": "#/components/parameters/SignerId"}], "responses": {"401": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The reminder was sent.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signer"}}}}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"$ref": "#/components/responses/Problem"}, "429": {"description": "A reminder was sent to this signer less than an hour ago.", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}, "/v1/signings/{signingId}/simulate": {"post": {"tags": ["Signings"], "operationId": "simulateSigning", "summary": "Drive a sandbox signing to an outcome", "description": "Sandbox keys only. Moves a signing to `completed`, `declined` or\n`expired` so an integrator can exercise each terminal path without\nwaiting. `complete` simulates the lifecycle: signer status is\n`simulated`, completion mode is `sandbox_simulation`, and no\nsignature is created. It does return a downloadable artifact so the\nsuccess path is testable, flagged `sandbox: true`. The sandbox adds\nno signature: an unsigned PDF comes back stamped and unsigned,\nanything already signed comes back byte-identical.\n", "parameters": [{"$ref": "#/components/parameters/SigningId"}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"action": {"type": "string", "enum": ["complete", "decline", "expire"]}, "signerEmail": {"type": "string", "format": "email", "description": "Which signer declines. Defaults to the first."}, "reason": {"type": "string"}}, "required": ["action"], "additionalProperties": false}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "The signing after the simulated outcome.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Signing"}}}}, "400": {"$ref": "#/components/responses/Problem"}, "403": {"description": "The key is a production key.", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}, "404": {"$ref": "#/components/responses/Problem"}, "409": {"description": "The signing is already terminal, so there is no outcome left to\nsimulate.\n", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}}}}, "webhooks": {"signingEvent": {"post": {"summary": "Signing event", "description": "Sent to `webhookUrl` as a signing progresses. Deliveries carry\n`X-Tvarka-Signature: sha256=<HMAC-SHA256 of the raw body>` keyed with\nyour webhook secret - verify it before acting - plus\n`X-Tvarka-Idempotency-Key` and `X-Tvarka-Event`.\n\nRetried with backoff on 5xx and transport failures. Delivery is a\nconvenience, never the only way to learn an outcome: polling always\nworks, and a failed delivery never changes a signing's state.\n\nThe payload never carries a signer's identity data - no personal\ncode, no certificate subject, no phone number. It echoes the email\nyou supplied and nothing more.\n", "requestBody": {"content": {"application/json": {"schema": {"type": "object", "properties": {"event": {"type": "string", "enum": ["signing.signer_signed", "signing.signer_declined", "signing.completed", "signing.declined", "signing.cancelled", "signing.expired", "signing.failed"]}, "signingId": {"type": "string", "format": "uuid"}, "status": {"$ref": "#/components/schemas/SigningStatus"}, "externalId": {"type": "string"}, "occurredAt": {"type": "string", "format": "date-time"}, "completionMode": {"type": "string", "enum": ["qes_ceremony", "sandbox_simulation"]}, "signer": {"type": "object", "properties": {"signerId": {"type": "string", "format": "uuid"}, "email": {"type": "string", "format": "email"}, "status": {"$ref": "#/components/schemas/SignerStatus"}, "declineReason": {"type": "string"}}}, "document": {"$ref": "#/components/schemas/SignedDocument"}}}}}}, "responses": {"401": {"$ref": "#/components/responses/Problem"}, "429": {"$ref": "#/components/responses/Problem"}, "200": {"description": "Acknowledged. Any 2xx stops redelivery."}}}}}, "components": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "description": "`Authorization: Bearer <key>`. Keys are provisioned by Tvarka during\nonboarding and shown once. A `tsk_live_` key acts on production; a\n`tsk_test_` key is the sandbox.\n\nThe key acts as a named workspace user. If that person's role is\nrevoked, or the workspace is suspended, the key stops working.\n"}}, "parameters": {"BatchId": {"name": "batchId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, "FileToken": {"name": "fileToken", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, "SigningId": {"name": "signingId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}, "SignerId": {"name": "signerId", "in": "path", "required": true, "schema": {"type": "string", "format": "uuid"}}}, "responses": {"Problem": {"description": "An RFC 9457 problem document.", "content": {"application/problem+json": {"schema": {"$ref": "#/components/schemas/Problem"}}}}}, "schemas": {"CreateSigningRequest": {"type": "object", "properties": {"title": {"type": "string", "maxLength": 255, "description": "Shown to signers on the ceremony page and in their email."}, "document": {"type": "object", "properties": {"name": {"type": "string", "description": "Filename with extension. The extension selects the family:\n`.pdf`; `.asice`/`.sce`/`.bdoc` to countersign an existing\nappendable ASiC-E; `.adoc`/`.bedoc`/`.cedoc`/`.gedoc`/\n`.ggedoc` to countersign a signed ADOC-family container.\nFor containers the extension only decides that we look\ninside - the container's own mimetype member decides what\nit is. `.mdoc` is refused: an MDOC takes a first signature\nonly.\n"}, "content": {"type": "string", "format": "byte", "description": "Base64-encoded bytes, up to 15 MiB decoded. Provide exactly\none of `content` or `fileToken`.\n"}, "fileToken": {"type": "string", "format": "uuid", "description": "A reusable upload from `POST /v1/files`, referenced instead\nof carrying the bytes again. The bytes are copied into the\nsigning at creation, so purging the file later never\nreaches into a signing that already used it. Provide\nexactly one of `content` or `fileToken`.\n"}, "flatten": {"type": "boolean", "default": false, "description": "PDF only. Draw every form field's appearance into page\ncontent and drop the AcroForm, so what a reader sees cannot\ndepend on a viewer's form handling. Fails closed: if any\nwidget has no appearance stream Tvarka can draw faithfully,\nnothing is flattened and the response names those fields.\nSigning a live AcroForm without this is perfectly valid -\nthe signature still detects any later edit.\n"}}, "required": ["name"], "additionalProperties": false}, "container": {"type": "object", "description": "Compose a NEW container from several files and sign that, instead\nof posting one finished document. Mutually exclusive with\n`document`; exactly one of the two is required.\n", "properties": {"format": {"type": "string", "enum": ["asice", "adoc"], "description": "`asice` takes PDFs only. `adoc` is the Lithuanian ADOC-V1.0\npackage and takes `.pdf` and `.xlsx`. The first file is the\nmain document; the rest are appendices.\n"}, "files": {"type": "array", "minItems": 1, "maxItems": 20, "items": {"type": "object", "properties": {"name": {"type": "string"}, "content": {"type": "string", "format": "byte"}}, "required": ["name", "content"], "additionalProperties": false}}}, "required": ["format", "files"], "additionalProperties": false}, "signers": {"type": "array", "minItems": 1, "maxItems": 20, "description": "Everyone who must sign. Unordered, and one email may appear\nonce - a person signs a document once.\n", "items": {"$ref": "#/components/schemas/SignerRequest"}}, "delivery": {"type": "string", "enum": ["link", "email"], "default": "link", "description": "`link` returns a `ceremonyUrl` per signer for you to deliver.\n`email` has Tvarka email each signer in your entity's name.\n"}, "webhookUrl": {"type": "string", "format": "uri", "description": "Public HTTPS URL for signing events. Optional."}, "externalId": {"type": "string", "maxLength": 120, "description": "Your own reference, echoed on every response and webhook."}, "signingOrder": {"type": "string", "enum": ["parallel", "sequential"], "default": "parallel"}, "softDeadlineInDays": {"type": "integer", "minimum": 1, "description": "Sets the soft deadline. On the day it passes, every signer who can\nstill sign is reminded once. Independent of `expiresInDays`, which\nis the hard deadline.\n"}, "expiresInDays": {"type": "integer", "minimum": 1, "maximum": 30, "default": 14}}, "required": ["title", "signers"], "oneOf": [{"required": ["document"]}, {"required": ["container"]}], "description": "Exactly one of `document` (a finished file) or `container` (several\nfiles composed into a new one) is required.\n", "additionalProperties": false}, "SignerRequest": {"type": "object", "properties": {"role": {"type": "string", "enum": ["signer", "viewer"], "default": "signer", "description": "A `viewer` is recorded but never invited, never gets a ceremony\nlink, and never holds up completion. Tvarka sends them nothing.\nAt least one signer is required.\n"}, "email": {"type": "string", "format": "email"}, "name": {"type": "string", "maxLength": 500}, "language": {"type": "string", "enum": ["lt", "en"], "default": "lt", "description": "Per signer, for their email and ceremony page."}, "personalCode": {"type": "string", "pattern": "^[0-9]{11}$", "description": "Optional Lithuanian identity lock. Tvarka stores a keyed hash\nand encrypted value, never returns the code, and refuses a\ndifferent authenticated signer.\n"}, "methods": {"type": "array", "minItems": 1, "uniqueItems": true, "default": ["nfc", "smart_id", "mobile_id"], "items": {"$ref": "#/components/schemas/SigningMethod"}, "description": "Per-signer allow-list. `nfc` uses the Tvarka Sign mobile app;\nSmart-ID and Mobile-ID use the hosted page or mobile app.\n"}}, "required": ["email"], "additionalProperties": false}, "SigningList": {"type": "object", "properties": {"data": {"type": "array", "items": {"$ref": "#/components/schemas/Signing"}}, "hasMore": {"type": "boolean"}, "nextCursor": {"type": "string", "format": "uuid", "nullable": true}}, "required": ["data", "hasMore", "nextCursor"], "additionalProperties": false}, "Signing": {"type": "object", "properties": {"signingId": {"type": "string", "format": "uuid"}, "status": {"$ref": "#/components/schemas/SigningStatus"}, "title": {"type": "string"}, "delivery": {"type": "string", "enum": ["link", "email"]}, "signingOrder": {"type": "string", "enum": ["parallel", "sequential"], "description": "`sequential` releases one signer at a time in the order they were\nposted. The signers who are waiting hold a ceremony link that\nanswers 404 until their turn, so the ordering is enforced rather\nthan merely unsent. A decline stops the workflow at the\ndecliner's turn: the signing settles `declined` and the signers\nstill waiting are cancelled.\n"}, "softDeadlineAt": {"type": "string", "format": "date-time", "nullable": true, "description": "The date the signing is wanted by, as opposed to `expiresAt`,\nwhich is the date it dies on. Nothing expires here; it is when the\none automatic reminder goes out.\n"}, "externalId": {"type": "string", "nullable": true}, "createdAt": {"type": "string", "format": "date-time"}, "expiresAt": {"type": "string", "format": "date-time", "nullable": true}, "completedAt": {"type": "string", "format": "date-time", "nullable": true}, "completionMode": {"type": "string", "enum": ["qes_ceremony", "sandbox_simulation"], "nullable": true, "description": "`qes_ceremony` means human eID signing. `sandbox_simulation`\nexercises lifecycle/webhook integration and creates no\nsignature; its artifact is flagged `sandbox: true`.\n"}, "erasedAt": {"type": "string", "format": "date-time", "nullable": true}, "detail": {"type": "string", "description": "Present when the status needs a sentence of explanation."}, "signers": {"type": "array", "items": {"$ref": "#/components/schemas/Signer"}}, "document": {"$ref": "#/components/schemas/SignedDocument"}}, "additionalProperties": false}, "Signer": {"type": "object", "properties": {"signerId": {"type": "string", "format": "uuid"}, "role": {"type": "string", "enum": ["signer", "viewer"], "description": "A `viewer` is a recorded non-signing party: no invitation is\nraised, no ceremony link exists, and they never hold up\ncompletion. Tvarka sends them nothing - you hold the webhook and\nthe download, and distribute. At least one signer is required.\n"}, "email": {"type": "string", "format": "email", "nullable": true}, "name": {"type": "string", "nullable": true}, "language": {"type": "string", "enum": ["lt", "en"]}, "status": {"$ref": "#/components/schemas/SignerStatus"}, "ceremonyUrl": {"type": "string", "format": "uri", "description": "Present only while this signer can still sign. A finished or\nretracted invitation has no live URL.\n"}, "signedAt": {"type": "string", "format": "date-time", "nullable": true}, "declinedAt": {"type": "string", "format": "date-time", "nullable": true}, "declineReason": {"type": "string"}, "methodsAllowed": {"type": "array", "items": {"$ref": "#/components/schemas/SigningMethod"}}, "actualMethod": {"allOf": [{"$ref": "#/components/schemas/SigningMethod"}], "nullable": true, "description": "Present after a real signature; contains no identity data."}, "identityLocked": {"type": "boolean"}, "identityMatched": {"type": "boolean", "nullable": true, "description": "True only after a locked signer completes the real ceremony."}, "simulatedAt": {"type": "string", "format": "date-time", "nullable": true}}, "additionalProperties": false}, "Batch": {"type": "object", "properties": {"batchId": {"type": "string", "format": "uuid"}, "title": {"type": "string"}, "externalId": {"type": "string", "nullable": true}, "createdAt": {"type": "string", "format": "date-time"}, "signings": {"type": "array", "items": {"$ref": "#/components/schemas/Signing"}}}}, "StoredFile": {"type": "object", "properties": {"fileToken": {"type": "string", "format": "uuid"}, "name": {"type": "string"}, "mediaType": {"type": "string"}, "sha256": {"type": "string"}, "bytes": {"type": "integer"}, "createdAt": {"type": "string", "format": "date-time"}, "expiresAt": {"type": "string", "format": "date-time", "description": "An unreferenced upload is not kept forever just because nobody\ndeleted it. Re-upload after this, or post the bytes inline.\n"}, "usedCount": {"type": "integer", "description": "How many signings have copied these bytes."}}}, "Comment": {"type": "object", "properties": {"commentId": {"type": "string", "format": "uuid"}, "author": {"type": "string", "enum": ["client", "tvarka"]}, "authorLabel": {"type": "string", "nullable": true}, "body": {"type": "string"}, "createdAt": {"type": "string", "format": "date-time"}}}, "SignedDocument": {"type": "object", "description": "The newest signed output. Absent until somebody signs, except on a\nsandbox simulation, where it describes the unsigned artifact.\n", "properties": {"sha256": {"type": "string"}, "bytes": {"type": "integer"}, "mimeType": {"type": "string"}, "sandbox": {"type": "boolean", "description": "Present and true only on a sandbox simulation. The bytes are a\nreal file to which no signature was added.\n"}, "signed": {"type": "boolean", "description": "Present and false only on a sandbox simulation. Absent on a real\nsigning, where the output is signed by definition.\n"}}}, "SigningStatus": {"type": "string", "enum": ["pending", "completed", "declined", "cancelled", "expired", "failed"], "description": "`pending` - at least one signer can still sign.\n`completed` - every signer signed.\n`declined` - somebody declined and nobody else can still sign.\n`expired` - the window closed first.\n`cancelled` - you retracted it.\n`failed` - the signing could not be prepared.\n"}, "SignerStatus": {"type": "string", "enum": ["pending", "signed", "declined", "cancelled", "expired", "simulated", "waiting", "recorded"], "description": "`waiting` - a sequential signer whose turn has not come; their\nceremony link answers 404 until it does.\n`recorded` - a viewer. Never invited, never blocks completion.\n"}, "SigningMethod": {"type": "string", "enum": ["nfc", "smart_id", "mobile_id"], "description": "The orchestration methods. For direct LT ATK reader, USB, raw CAdES\nand lower-level NFC integration, use the ATK API.\n"}, "ProblemFieldError": {"type": "object", "description": "One rejected field.", "properties": {"pointer": {"type": "string", "description": "RFC 6901 JSON Pointer into the request body, for example\n`/signers/0/email` or `/container/files/2/content`.\n"}, "detail": {"type": "string"}}, "required": ["pointer", "detail"]}, "Problem": {"type": "object", "description": "RFC 9457.", "properties": {"type": {"type": "string", "format": "uri"}, "title": {"type": "string"}, "status": {"type": "integer"}, "detail": {"type": "string"}, "errors": {"type": "array", "description": "Present on `validation-failed`, and on the single-fault problems\nthat name a field. Request-body validation collects rather than\nfailing fast, so a caller who got three fields wrong learns all\nthree from one response.\n", "items": {"$ref": "#/components/schemas/ProblemFieldError"}}, "recoveryUrl": {"type": "string", "format": "uri-reference", "description": "Present on the payment-required refusal when a web page exists\nthat fixes it: the tenant's payment setup, reached with an\nordinary browser login. Show it to your operator; it is not an\nAPI endpoint.\n"}}}}}}