Nexiel Sign issues a Qualified Electronic Signature (QES, eIDAS Art. 3(12)) on a PDF document, attributing it to one specific natural person. It lives on Nexiel Screen as a single route, POST /v1/sign, not a new service and not a wallet session flow: send a document, get back a signed one.
There is no sandbox mode for this route, and no example below runs against a live QTSP. A QES is a real legal act with real consequences for the signer, so POST /v1/sign requires a real Keycloak-authenticated bearer token in every service mode, including a self-run sandbox instance. See production status below for the real, current gap this is waiting on.
Nexiel Screen also issues a Qualified Electronic Seal (QSeal, eIDAS Art. 3(25)) at POST /v1/seal. A QSeal attributes a document to a legal person, the company itself, using that company's own single, pre-provisioned credential. A QES attributes a document to a natural person, the individual who signed it, using that person's own, individually provisioned credential. Nexiel Sign is the individual counterpart to Nexiel Seal, not a variant of the same check with a flag flipped: the two routes have separate authorization models, separate database tables, and separate error types, because they are separate real-world acts.
Both routes use the same underlying mechanism: a CSC (Cloud Signature Consortium) API v2.2 credential held at a QTSP (Qualified Trust Service Provider), never a shared Nexiel credential. Nexiel never stores the document itself, before or after signing, only SHA-256 hashes of both.
The visible signer name embedded in a signed PDF is KYB representative <signerId>, not a decrypted legal name. Nexiel Sign never decrypts the signer's stored personal data to populate this field. An integration that wants the signer's real name in the visible signature would need to supply it explicitly as part of its own flow (for example, from its own UI, where the signer types it themselves), never read silently out of Nexiel's records.
POST /v1/sign runs the following checks, in this order, before it signs anything:
401.503 qes_not_configured, before signerId is evaluated at all.Idempotency-Key header, enforced server-side, exactly like POST /v1/screen.signerId resolving to a real, declared UBO, director, or senior managing official on an APPROVED KYB application for the calling client. Only a real, on-file person may sign. No free-text name is accepted.kyb_ubos.qes_credential_id). This is per-individual provisioning, unlike Nexiel Seal's one credential per client: each declared representative who signs needs their own credential provisioned before their first signature.The last two checks, and the actual signature, only run once a real QTSP account exists for this deployment. Today, step 3 above stops every request first. The error responses for steps 6 and 7 below are real and independently tested against the full gating pipeline. You will not observe them from a live request against a deployment with no QTSP configured, because the request never reaches that far.
An optional pin and pinObjectIdpair is forwarded to the QTSP as the CSC API's authDataobject, to satisfy eIDAS Art. 26's “sole control” requirement for a remote qualified signature. Nexiel never validates this PIN itself. It is opaque, forwarded as-is, and never logged or persisted anywhere in this codebase.
The real request and response schema for POST /v1/sign. Because this route always fails closed today (see production status below), the response shown is the documented schema, not a captured live response:
curl -s -X POST https://compliance.nexiel.io/v1/sign \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access_token>" \
-H "Idempotency-Key: 6c1a9e4f-2b6d-4830-9a1c-7e9b2d4f6a83" \
-d '{
"document": "JVBERi0xLjQKJcOkw7zDtsO...(base64-encoded PDF, truncated)",
"signerId": "9d2b4f61-7a83-4c15-b6e2-8f04d3a91c67",
"pin": "4471",
"pinObjectId": "PIN"
}'{
"operationId": "3f0a7c1e-9b6d-4a15-8305-c1e7b9d2f4a6",
"signedDocument": "JVBERi0xLjQKJcOkw7zDtsO...(base64-encoded, truncated)",
"documentHashSha256": "7c1a9e4f2b6d8305a1c7e9b2d4f6a8305c1e7b9d2f4a6c8305f1a9c7e2b4d6f8",
"signedDocumentHashSha256": "4a7f1e9c2b6d8305f1a9c7e2b4d6f8305a1c7e9b2d4f6a8305c1e7b9d2f4a6c8",
"frozenDecisionArtifact": {
"cscCredentialId": "signer-credential-9d2b4f61",
"qtspProvider": "d-trust",
"engineVersion": "0.1.0-provisional",
"cscApiSpecVersion": "2.2.0.0",
"subFilter": "ETSI.CAdES.detached"
}
}document is base64-encoded PDF bytes, up to roughly an 11MB decoded ceiling. signerId is a kyb_ubos.id, never a free-text name. pin and pinObjectId are optional together, and omitted entirely for a non-interactive credential. frozenDecisionArtifact pins exactly which CSC credential, QTSP, engine version, and CSC API specification version produced the signature, the same regulator-facing discipline every screening decision in Nexiel Screen records.
In the order the gating chain above can produce them:
{
"error": "unauthorized",
"message": "invalid or missing bearer token"
}{
"error": "account_not_active",
"message": "this account is not currently active"
}{
"error": "qes_not_configured",
"message": "Nexiel Sign is not configured for this deployment (QSEAL_CSC_BASE_URL / QSEAL_CSC_OAUTH2_TOKEN_URL / QSEAL_CSC_CLIENT_ID / QSEAL_CSC_CLIENT_SECRET are unset). no real QTSP account has been provisioned yet."
}{
"error": "no_linked_kyb_client",
"message": "no clients.id is linked to this API key. Nexiel Sign requires a real, KYB-verified legal entity."
}{
"error": "signer_not_authorized",
"message": "signerId does not resolve to a declared UBO/director/senior-managing-official on an APPROVED KYB application for this client. only this client's own declared representatives may sign."
}{
"error": "signer_credential_not_provisioned",
"message": "signer 9d2b4f61-7a83-4c15-b6e2-8f04d3a91c67 has no QES credential provisioned yet (kyb_ubos.qes_credential_id is null)."
}{
"error": "idempotency_key_conflict",
"message": "idempotency key already used with a different request"
}qes_operationsrow per signature: the request hash, the KYB-verified client id, the signer's kyb_ubos.id, both document hashes, and the frozen cscCredentialId, qtspProvider, engineVersion, cscApiSpecVersion, and subFilter values used for that signature.QES_ISSUED audit log entry carrying the same operation id and hashes, logged immutably like every other credential-issuance action in this codebase.pin or pinObjectId value, anywhere: not in qes_operations, not in the audit log, not in application logs.No real QTSP account exists yet for Nexiel Sign, or for Nexiel Seal. Every real deployment of this codebase today has qesSigner unset, so POST /v1/sign always returns 503 qes_not_configuredonce an authenticated, active-account caller reaches that check. This is a real, tested scaffold built against the published CSC API v2.2 specification: every gate described above, the CMS/PAdES signing pipeline underneath it, and the audit trail it writes are real code with real test coverage, including a cryptographic round-trip test (a real PDF, a real RSA keypair standing in for a QTSP's hardware security module, independent re-verification of the result via a separate code path). What is missing is a business relationship with a QTSP, not code. Provisioning one is a founder-level decision, not an engineering task.
Unlike most check types documented on this site, there is deliberately no sandbox-mode version of this route to try in the meantime. A QES is a real legal act with real consequences for the person signing, and a fake, unauthenticated version of it would risk a caller mistaking a synthetic signature for a real one.
There is no separate Nexiel Sign API reference page. POST /v1/sign is documented, with its full request and response schema, on the Screen API reference. See the Screen API quickstart's authentication sectionfor how to exchange a KYB-approved client's credentials for the bearer token this route requires.