{
  "openapi": "3.1.0",
  "info": {
    "title": "SecurySign API",
    "version": "2026-09-26",
    "summary": "Identity infrastructure: sign-in, identity verification, enrolment, certificates, signing and events.",
    "description": "API endpoints for identity verification, signing, enrolment and document encryption. Each operation documents its authentication, inputs, responses and errors; the integration guides provide complete request examples.",
    "contact": {
      "name": "SecurySign support",
      "email": "support@tenda.world"
    }
  },
  "servers": [
    {
      "url": "https://securysign.com/api",
      "description": "Production"
    },
    {
      "url": "https://signa.dev.securysign.com/api",
      "description": "Sandbox"
    }
  ],
  "tags": [
    {
      "name": "Signing tokens",
      "description": "Document-bound signing tokens and passkey approval through the signing frame.",
      "externalDocs": {
        "description": "Integration: Iframe",
        "url": "#/docs/iframe"
      }
    },
    {
      "name": "Hash signing",
      "description": "Signing requests for document hashes, with passkey approval and completion results.",
      "externalDocs": {
        "description": "Workflow: Hash signing",
        "url": "#/docs/hash-signing"
      }
    },
    {
      "name": "PAdES signing",
      "description": "PDF preparation, passkey approval and retrieval of the signed PDF.",
      "externalDocs": {
        "description": "Workflow: PAdES signing",
        "url": "#/docs/pades-signing"
      }
    },
    {
      "name": "Webhooks",
      "description": "Completion notifications signed with the subscription secret.",
      "externalDocs": {
        "description": "Hash signing API: completion events",
        "url": "#/docs/api-hash-signing#receive-completion-events"
      }
    },
    {
      "name": "Identity verification",
      "description": "Read document details and the portrait, check face-capture liveness, and compare the captured face with the document portrait. The verdict reports the outcome of these checks.",
      "externalDocs": {
        "description": "Workflow: KYC",
        "url": "#/docs/kyc"
      }
    },
    {
      "name": "Phone handoff",
      "description": "Capture on a phone through a hosted link. The backend creates the handoff; the hosted phone page calls `/kyc/handoff/*`.",
      "externalDocs": {
        "description": "Workflow: KYC, phone handoff",
        "url": "#/docs/kyc#6-optional-continue-on-a-phone"
      }
    },
    {
      "name": "Liveness capture",
      "description": "Capture SDK requests forwarded through the backend pass-through. The SDK makes these requests as part of capture.",
      "externalDocs": {
        "description": "SDK reference: pass-through",
        "url": "#/docs/sdk-reference#add-the-pass-through"
      }
    },
    {
      "name": "Relying parties",
      "description": "Application registration, credentials, scopes, origins, callbacks and usage, managed by the contact account.",
      "externalDocs": {
        "description": "Integration: RP integration guide",
        "url": "#/docs/rp-integration-guide"
      }
    },
    {
      "name": "Identity providers",
      "description": "Corporate identity-provider registration and broker callbacks.",
      "externalDocs": {
        "description": "Integration: IdP integration guide",
        "url": "#/docs/idp-integration-guide"
      }
    },
    {
      "name": "Certificates",
      "description": "Certificate retrieval, status checks, renewal and passkey-approved revocation.",
      "externalDocs": {
        "description": "Workflow: Verification",
        "url": "#/docs/verification"
      }
    },
    {
      "name": "Encryption",
      "description": "Storage of device-encrypted files and authenticated decryption by the key owner.",
      "externalDocs": {
        "description": "Workflow: Encryption and decryption",
        "url": "#/docs/encryption"
      }
    },
    {
      "name": "Visible signature",
      "description": "Retrieval and verification of a signed signature image.",
      "externalDocs": {
        "description": "Workflow: Enrolment",
        "url": "#/docs/enrolment#read-the-signers-certificate-and-drawn-signature"
      }
    },
    {
      "name": "Plans",
      "description": "Plan prices and signing allowances.",
      "externalDocs": {
        "description": "Resources: Pricing and limits",
        "url": "#/docs/limits"
      }
    },
    {
      "name": "Sign-in (OIDC)",
      "description": "OpenID Connect authorization and code exchange for user tokens.",
      "externalDocs": {
        "description": "Workflow: Single sign-on",
        "url": "#/docs/sso"
      }
    },
    {
      "name": "Enrolment (OIDC)",
      "description": "Hosted enrolment and authorization-code exchange after completion.",
      "externalDocs": {
        "description": "Workflow: Enrolment",
        "url": "#/docs/enrolment#start-the-journey"
      }
    }
  ],
  "paths": {
    "/ssc/token": {
      "post": {
        "tags": [
          "Signing tokens"
        ],
        "operationId": "createSigningToken",
        "summary": "Create a signing token",
        "description": "Issues a token for one document hash. Send the SSC secret and the complete SHA-256 hexadecimal hash from the backend, then pass the token to the iframe. It expires after five minutes. A LOA-4 request also includes the signer’s email, binding approval to their passkey.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SigningTokenRequest"
              },
              "example": {
                "clientId": "signa-rp-42",
                "clientSecret": "<ssc-secret>",
                "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                "loa": "LOA-2"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Signing token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningToken"
                },
                "example": {
                  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IlNTQy1TSUdOSU5HLVRPS0VOIn0…",
                  "expiresAt": 1790000300,
                  "loa": "LOA-2",
                  "credentialBound": false
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "description": "`Unknown RP client_id`, `RP registration not approved. Status: pending`, `Invalid client credentials`, `Requested LOA LOA-4 exceeds RP maximum LOA-2` or `LOA-4 requires KYC-verified RP`: check your client ID, SSC secret, approval and maximum LOA.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid client credentials"
                }
              }
            }
          },
          "404": {
            "description": "LOA-4 only: `User not found for email: …` or `No credential found for user`: the signer must enrol before you request LOA-4.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No credential found for user"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "x-credential": {
          "name": "SSC secret",
          "description": "Send your Secure Signature Confirmation (SSC) secret as `clientSecret` in the JSON body, from your server only."
        }
      }
    },
    "/ssc/challenge": {
      "post": {
        "tags": [
          "Signing tokens"
        ],
        "operationId": "createSscChallenge",
        "summary": "Create a signing challenge",
        "description": "The signing frame submits the token and document hash to obtain a passkey challenge. `rpOrigin` must identify an authorized embedding origin.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SscChallengeRequest"
              },
              "example": {
                "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                "documentName": "Contract.pdf",
                "rpOrigin": "https://app.example.com",
                "mode": "registered",
                "token": "eyJhbGciOiJIUzI1NiIs…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The iframe gets the challenge. `credentialID` is set only for LOA-4.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SscChallenge"
                },
                "example": {
                  "operationId": "ssc_abc123",
                  "challenge": "b64url-challenge",
                  "credentialID": null,
                  "expiresAt": "2026-09-26T12:05:00Z",
                  "rpId": "securysign.com",
                  "mode": "registered"
                }
              }
            }
          },
          "403": {
            "description": "The embedding origin is not one of your authorised signing origins: authorise it on the RP dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP origin not authorized: https://app.example.com"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ssc/finalize": {
      "post": {
        "tags": [
          "Signing tokens"
        ],
        "operationId": "finalizeSscSignature",
        "summary": "Finalize a signature",
        "description": "The frame submits the passkey assertion for the operation. SecurySign verifies it and creates the signature, which the frame sends to the parent page as `SSC_SIGN_COMPLETE`.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SscFinalizeRequest"
              },
              "example": {
                "operationId": "ssc_abc123",
                "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                "assertion": {
                  "id": "cred-id",
                  "rawId": "b64url",
                  "type": "public-key",
                  "response": {
                    "authenticatorData": "b64url",
                    "clientDataJSON": "b64url",
                    "signature": "b64url",
                    "userHandle": "b64url"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The iframe gets the signature and posts it to your page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SscSignature"
                },
                "example": {
                  "signatureId": "ssc_sig_42",
                  "signatureBase64": "MEUCIQDxY…",
                  "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                  "userVerified": true,
                  "credentialId": "signing-key-abc123",
                  "timestamp": 1790000200,
                  "mode": "registered",
                  "levelOfAssurance": "LOA-2"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "403": {
            "description": "The passkey approval failed verification: `Passkey signature verification failed`, `Challenge mismatch — dynamic linking failed`, `Origin mismatch — possible phishing attack`, `User verification required — biometric or PIN not confirmed`, `Passkey was created for a different site (RP ID mismatch)`, `Credential not registered`. Have your user approve again on a SecurySign page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Passkey signature verification failed"
                }
              }
            }
          },
          "410": {
            "description": "The operation expired after 5 minutes, or does not exist: request a new token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Operation expired or not found"
                }
              }
            }
          }
        }
      }
    },
    "/v2/sign/single": {
      "post": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "createSigningRequest",
        "summary": "Create a signing request",
        "description": "Creates a pending request from a document hash and passkey ID, authenticated with the customer’s access token. Save the returned `requestId` with the document and open it in the signing frame for approval.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SigningRequestCreate"
              },
              "example": {
                "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                "credentialId": "cred_abc123",
                "documentName": "Contract.pdf",
                "signatureFormat": "PAdES-B-LT",
                "loa": "LOA-2",
                "callbackUrl": "https://app.example.com/hooks/sign",
                "rpOrigin": "https://app.example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Challenge for passkey approval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningRequestPending"
                },
                "example": {
                  "requestId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                  "status": "pending_auth",
                  "authUrl": "/api/v2/sign/auth/a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                  "challenge": "b64-webauthn-challenge"
                }
              }
            }
          },
          "400": {
            "description": "`documentHash and credentialId required`: send both.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "documentHash and credentialId required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "`No signing credential found for user`: send one of this user's own passkey IDs as `credentialId`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No signing credential found for user"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v2/sign/auth/{requestId}": {
      "post": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "completeSigningRequest",
        "summary": "Complete a signing request",
        "description": "Completes a pending request using the passkey assertion submitted by the signing frame. The response contains the signature and completion status.",
        "security": [],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "description": "The `requestId` you got when you created the request.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "assertion"
                ],
                "properties": {
                  "assertion": {
                    "$ref": "#/components/schemas/WebAuthnAssertion"
                  }
                }
              },
              "example": {
                "assertion": {
                  "id": "cred-id",
                  "rawId": "b64url",
                  "type": "public-key",
                  "response": {
                    "authenticatorData": "b64url",
                    "clientDataJSON": "b64url",
                    "signature": "b64url"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completed signature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SigningComplete"
                },
                "example": {
                  "status": "completed",
                  "signature": "MEUCIQDxY…",
                  "timestamp": "2026-09-26T12:00:04+00:00",
                  "loa": "LOA-2"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "description": "`Signing request not found or already processed`: check the request ID, or create a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Signing request not found or already processed"
                }
              }
            }
          },
          "403": {
            "description": "The passkey approval failed verification: `Passkey signature verification failed`, `Challenge mismatch — dynamic linking failed`, `Origin mismatch — possible phishing attack`, `User verification required — biometric or PIN not confirmed`, `Passkey was created for a different site (RP ID mismatch)`, `Credential not registered`. Have your user approve again on a SecurySign page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Passkey signature verification failed"
                }
              }
            }
          }
        },
        "x-credential": {
          "name": "Passkey assertion",
          "description": "Send the WebAuthn assertion your user made over this request's challenge; the request ID identifies the request."
        }
      }
    },
    "/v2/sign/batch": {
      "post": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "createBatch",
        "summary": "Create a batch",
        "description": "Creates a batch from document IDs, hashes and the customer’s passkey ID. Open the returned `batchId` in the signing frame to present the list for a single approval.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchCreate"
              },
              "example": {
                "documents": [
                  {
                    "id": "doc-1",
                    "hash": "a3f7c2d800000000000000000000000000000000000000000000000000000000",
                    "hash_algo": "2.16.840.1.101.3.4.2.1"
                  },
                  {
                    "id": "doc-2",
                    "hash": "9b2e41f000000000000000000000000000000000000000000000000000000000",
                    "hash_algo": "2.16.840.1.101.3.4.2.1"
                  }
                ],
                "credentialId": "cred_abc123",
                "callbackUrl": "https://app.example.com/hooks/batch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Challenge for passkey approval.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchPending"
                },
                "example": {
                  "batchId": "f0e1d2c3-b4a5-4968-8776-655443322110",
                  "status": "pending_auth",
                  "documentCount": 2,
                  "authUrl": "/api/v2/sign/batch/f0e1d2c3-b4a5-4968-8776-655443322110/auth",
                  "challenge": "b64-webauthn-challenge"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v2/sign/batch/{batchId}/auth": {
      "post": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "completeBatch",
        "summary": "Complete a batch",
        "description": "Completes the batch using the passkey assertion submitted by the frame. The response contains an overall status and a result for each document. Match results to the original document IDs.",
        "security": [],
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "assertion"
                ],
                "properties": {
                  "assertion": {
                    "$ref": "#/components/schemas/WebAuthnAssertion"
                  }
                }
              },
              "example": {
                "assertion": {
                  "id": "cred-id",
                  "rawId": "b64url",
                  "type": "public-key",
                  "response": {
                    "authenticatorData": "b64url",
                    "clientDataJSON": "b64url",
                    "signature": "b64url"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch result. Check each entry in `results` for its individual outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchComplete"
                },
                "example": {
                  "status": "partial",
                  "results": [
                    {
                      "documentId": "doc-1",
                      "status": "signed",
                      "signature": "MEUCIQDxY…"
                    },
                    {
                      "documentId": "doc-2",
                      "status": "failed",
                      "error": "Invalid document hash"
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "403": {
            "description": "The passkey approval failed verification: `Passkey signature verification failed`, `Challenge mismatch — dynamic linking failed`, `Origin mismatch — possible phishing attack`, `User verification required — biometric or PIN not confirmed`, `Passkey was created for a different site (RP ID mismatch)`, `Credential not registered`. Have your user approve again on a SecurySign page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Passkey signature verification failed"
                }
              }
            }
          }
        },
        "x-credential": {
          "name": "Passkey assertion",
          "description": "Send the WebAuthn assertion your user made over this batch's challenge; the batch ID identifies the batch."
        }
      }
    },
    "/v2/events": {
      "get": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "streamSigningEvents",
        "summary": "Watch signing requests live",
        "description": "Open this stream with the customer’s access token to receive changes to their signing requests. It starts with `connected`, then reports `signing_request_created`, `signing_request_completed` or `signing_request_failed` roughly every three seconds. Track the last status per ID to avoid processing duplicate updates.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event stream.",
            "content": {
              "text/event-stream": {
                "example": "event: signing_request_completed\ndata: {\"id\":\"a1b2c3d4-…\",\"status\":\"signed\",\"document_hash\":\"a3f7…\",\"created_at\":\"2026-09-26 12:00:00\",\"signed_at\":\"2026-09-26 12:00:04\",\"error_message\":null}\n\n"
              }
            }
          }
        }
      }
    },
    "/v2/webhooks/register": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "registerWebhook",
        "summary": "Create a subscription",
        "description": "Registers a public HTTPS callback, event types and a subscription secret using the RP contact account’s access token. Generate the secret on the backend and use it to verify the HMAC-SHA256 signature on deliveries.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookCreate"
              },
              "example": {
                "url": "https://app.example.com/hooks/securysign",
                "events": [
                  "sign.complete",
                  "batch.complete"
                ],
                "secret": "whsec_choose_a_long_random_value"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Active subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptionId": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "const": "active"
                    }
                  }
                },
                "example": {
                  "subscriptionId": 7,
                  "status": "active"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v2/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List subscriptions",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook subscriptions for the authenticated RP contact account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookSubscription"
                  }
                },
                "example": [
                  {
                    "id": 7,
                    "rp_id": 42,
                    "url": "https://app.example.com/hooks/securysign",
                    "events": [
                      "sign.complete"
                    ],
                    "active": true,
                    "created_at": "2026-09-01 10:00:00"
                  }
                ]
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Lists webhook subscriptions and their event types for the RP contact account authenticated by the access token."
      }
    },
    "/v2/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a subscription",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removal result in `deleted`; `false` means the subscription belongs to another account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "deleted": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Removes a subscription by the ID returned during registration. Authenticate as the RP contact account. A successful removal returns `deleted: true`."
      }
    },
    "/kyc/verifications": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "startVerification",
        "summary": "Start a verification",
        "description": "Starts or resumes the latest verification for `customer_id` and `phone_number`. An existing verification returns `created: false` with its current `status`. When existing-identity comparison is enabled, `enrolled` indicates whether an identity is already on file. Include the optional `rp_urn` to record an application reference, alongside an explicit `customer_id`.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerificationStart"
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678",
                    "phone_number": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "phone_number": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New or existing verification for this `customer_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerificationHandle"
                },
                "example": {
                  "customer_id": "+254712345678",
                  "phone_number": "+254712345678",
                  "status": "pending",
                  "created": true,
                  "enrolled": false
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "403": {
            "$ref": "#/components/responses/KycNotEnabled"
          },
          "422": {
            "description": "SecurySign cannot accept your `customer_id`, `phone_number` or `rp_urn`, or you sent `rp_urn` without `customer_id`, and no verification is created. Read `error` for the reason: for example, `phone_number is required`, a missing country code, too many or too few digits for the country, a number range not in use, or an email address without a top-level domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id looks like a phone number without a country code: send it in international format, e.g. +254712345678"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "409": {
            "description": "The phone number belongs to another customer, or this `customer_id` is on file with a different phone number. Read `error` and send this customer's own registered phone number. The response also uses `409` when the `rp_urn` is assigned to another customer at your RP, or this customer already holds a different `rp_urn`; `error` states the `rp_urn` to send.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "phone_number +254712345678 already belongs to another customer: send this customer's own phone number"
                }
              }
            }
          }
        }
      }
    },
    "/kyc/customers/document": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "submitDocument",
        "summary": "Submit the ID",
        "description": "Submit base64 document images with `customer_id`, without a `data:` prefix. For a national ID, use front then back; for a passport, use the photo page. Capture at about 1600 pixels on the long edge and JPEG quality 0.85, keeping all edges visible. Processing returns the extracted fields and document status. SecurySign extracts the document fields and portrait. Acceptance uses a passing MRZ check, or the overall processing result when the MRZ check is absent or not performed. A failed MRZ check rejects the document.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "images"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "images": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "contentEncoding": "base64"
                    },
                    "description": "Send base64 JPEGs, without a `data:` prefix. You can also send a single `image` string."
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                }
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678",
                    "images": [
                      "/9j/4AAQSkZJRg…",
                      "/9j/4AAQSkZJRg…"
                    ]
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "images": [
                      "/9j/4AAQSkZJRg…",
                      "/9j/4AAQSkZJRg…"
                    ],
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict: `document_verified`, or `failed` with `document_not_authentic` (ask for new photos).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verdict"
                },
                "example": {
                  "customer_id": "+254712345678",
                  "status": "document_verified",
                  "verified_name": "JANE DOE",
                  "doc_type": "National ID",
                  "doc_number": "123456789",
                  "personal_number": "12345678",
                  "doc_expiry": null,
                  "nationality": "KEN",
                  "face_match_score": null,
                  "liveness_score": null,
                  "reasons": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "$ref": "#/components/responses/VerificationNotFound"
          },
          "409": {
            "description": "`This verification is already verified`: use its verdict. The response also uses `409` when `rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This verification is already verified"
                }
              }
            }
          },
          "422": {
            "description": "`At least one document image is required`: send the ID images. The response also uses `422` when the request has no `customer_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "At least one document image is required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/kyc/customers/liveness-session": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "openLivenessSession",
        "summary": "Open a liveness session",
        "description": "Creates a capture session for the identified customer, returning `{ url, token, expiresIn }`. Pass `url` and `token` to the capture component. The token is valid for that verification for 600 seconds; a new session invalidates the previous token.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "responses": {
          "200": {
            "description": "Scoped session: `url` and `token`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "const": "/api/kyc/faceapi"
                    },
                    "token": {
                      "type": "string"
                    },
                    "expiresIn": {
                      "type": "integer",
                      "const": 600
                    },
                    "customer_id": {
                      "type": "string",
                      "description": "The customer identifier assigned to the capture session."
                    },
                    "rp_urn": {
                      "type": "string",
                      "description": "The recorded RP reference when one is assigned to this customer."
                    }
                  }
                },
                "example": {
                  "customer_id": "+254712345678",
                  "url": "/api/kyc/faceapi",
                  "token": "eyJhbGciOi…",
                  "expiresIn": 600
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "$ref": "#/components/responses/VerificationNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/customers/face": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "submitLivenessResult",
        "summary": "Submit the liveness result",
        "description": "Submit the `transactionId` from a `confirmed` capture as `livenessTransactionId`. SecurySign evaluates the document, liveness and face match. Default thresholds are 0.50 for liveness and 0.75 for face match. With an accepted document and live capture, similarity from 0.65 to below 0.75 produces `needs_review` with `face_match_borderline`. Read `status` and `reasons` for the outcome.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "customer_id",
                  "livenessTransactionId"
                ],
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "livenessTransactionId": {
                    "type": "string",
                    "description": "Send the `transactionId` your capture returned."
                  },
                  "return_selfie": {
                    "type": "boolean",
                    "default": false,
                    "description": "Send `true` or `1` to get your customer's liveness selfie in `selfie`. The image is returned only when the verdict is `verified`. For any other status `selfie` is `null`, which means SecurySign has not verified this person: the face check is unfinished (`pending`, `document_verified`) or did not pass (`needs_review`, `failed`)."
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                }
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678",
                    "livenessTransactionId": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
                    "return_selfie": true
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "livenessTransactionId": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
                    "return_selfie": true,
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verdict"
                },
                "example": {
                  "customer_id": "+254712345678",
                  "status": "verified",
                  "verified_name": "JANE DOE",
                  "doc_type": "National ID",
                  "doc_number": "123456789",
                  "personal_number": "12345678",
                  "doc_expiry": null,
                  "nationality": "KEN",
                  "face_match_score": 0.91,
                  "liveness_score": 1.0,
                  "reasons": [],
                  "selfie": {
                    "content_type": "image/png",
                    "data": "iVBORw0KGgoAAAANSUhEUgAA…"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "description": "The verification is unknown, or the capture did not run through the session `url`: start the verification first, and route the capture through the session `url`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "KYC verification not found"
                }
              }
            }
          },
          "409": {
            "description": "`Submit the document before the liveness step`, `Resubmit the document before the liveness step` or `This verification is already verified`: submit the document first, or use the existing verdict. The response also uses `409` when `rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Submit the document before the liveness step"
                }
              }
            }
          },
          "422": {
            "description": "`A liveness transaction id is required`: send the capture's `transactionId`. The response also uses `422` when the request has no `customer_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "A liveness transaction id is required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/kyc/customers/verification": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "getVerification",
        "summary": "Get the verdict",
        "description": "Reads the current status and extracted fields for `customer_id` supplied in the POST body. Poll this endpoint during hosted capture. `status: \"verified\"` indicates success; other statuses and `reasons` identify the retry or review needed.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "responses": {
          "200": {
            "description": "Verification verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verdict"
                },
                "example": {
                  "customer_id": "+254712345678",
                  "status": "verified",
                  "face_match_score": 0.91,
                  "liveness_score": 1.0,
                  "reasons": []
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "$ref": "#/components/responses/VerificationNotFound"
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "return_selfie": {
                    "type": "boolean",
                    "default": false,
                    "description": "Send `true` or `1` to get your customer's liveness selfie in `selfie`: use it on your polls after a phone handoff. The image is returned only when the verdict is `verified`. While your customer is still on their phone (`pending`, `document_verified`), or if the face check did not pass (`needs_review`, `failed`), `selfie` is `null`."
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678",
                    "return_selfie": true
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "return_selfie": true,
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/customers/compare": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "compareCustomer",
        "summary": "Run a Live Check",
        "description": "Compares submitted details or a base64 photo with the customer’s verified identity on file. The response contains a match result for each requested detail and a similarity result for the photo. Identity lookup follows the configured scope. To establish a new verified identity, complete document and live face capture first.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerCompareRequest"
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678",
                    "checks": {
                      "personal_number": "12345678",
                      "date_of_birth": "31/01/1990",
                      "full_name": "Doe, Jane Wanjiru"
                    },
                    "image": "/9j/4AAQSkZJRg…"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "checks": {
                      "personal_number": "12345678",
                      "date_of_birth": "31/01/1990",
                      "full_name": "Doe, Jane Wanjiru"
                    },
                    "image": "/9j/4AAQSkZJRg…",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Whether the customer is enrolled, a result for each value, and the photo comparison.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomerCompareResult"
                },
                "example": {
                  "customer_id": "+254712345678",
                  "enrolled": true,
                  "matches": {
                    "customer_id": true,
                    "personal_number": true,
                    "date_of_birth": true,
                    "full_name": true
                  },
                  "face": {
                    "match": true,
                    "similarity": 0.9,
                    "threshold": 0.75
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "403": {
            "description": "You called with a user token: call from your backend with your RP credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Call this endpoint with your client credentials (HTTP Basic)"
                }
              }
            }
          },
          "409": {
            "description": "The identity on file has no portrait: leave out `image`, or verify the customer again. The response also uses `409` when `rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No document portrait is on file for this customer: send the checks only, or verify the customer again"
                }
              }
            }
          },
          "422": {
            "description": "Your `customer_id` is invalid (see Start a verification), `checks` is not an object, or it contains an attribute SecurySign does not check. Read `error` for the reason. The response also uses `422` when the request has no `customer_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "checks contains \"eye_colour\", which SecurySign does not check. Accepted: full_name, surname, given_names, document_number, personal_number, document_type, nationality, issuing_state, date_of_birth, date_of_expiry, date_of_issue, sex"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/kyc/customers/selfie": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "getLivenessSelfie",
        "summary": "Get the liveness selfie",
        "description": "Downloads the retained portrait frame as PNG for the supplied `customer_id` after a `verified` result. Unavailable evidence returns `404`; use the verification status separately to assess identity.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "responses": {
          "200": {
            "description": "PNG, sent with `Cache-Control: private, no-store`.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "image/png"
                }
              }
            }
          },
          "404": {
            "description": "Nothing is stored for this verification."
          },
          "403": {
            "description": "`The selfie is available only once the verification is verified.`: wait for a `verified` verdict.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "The selfie is available only once the verification is verified."
                }
              }
            }
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/customers/video": {
      "post": {
        "tags": [
          "Identity verification"
        ],
        "operationId": "getLivenessVideo",
        "summary": "Get the liveness recording",
        "description": "Downloads the retained capture recording as MP4 for the supplied `customer_id`. Evidence availability is reported separately from the verification verdict.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "responses": {
          "200": {
            "description": "MP4, sent with `Cache-Control: private, no-store`.",
            "content": {
              "video/mp4": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "video/mp4"
                }
              }
            }
          },
          "404": {
            "description": "No recording is stored for this verification."
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/customers/handoff": {
      "post": {
        "tags": [
          "Phone handoff"
        ],
        "operationId": "createHandoff",
        "summary": "Get a phone handoff link",
        "description": "Creates a hosted capture link for customers who want to use their phone. Display the returned `url` as a QR code and poll the verification result while capture is in progress. The link expires after 900 seconds.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "responses": {
          "200": {
            "description": "Link to show as a QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "customer_id": {
                      "type": "string",
                      "description": "The normalised customer identifier: an E.164 phone number or lowercase email address."
                    },
                    "url": {
                      "type": "string",
                      "description": "You display this hosted capture URL as a QR code for the customer."
                    },
                    "expiresIn": {
                      "type": "integer",
                      "const": 900
                    }
                  }
                },
                "example": {
                  "customer_id": "+254712345678",
                  "url": "https://securysign.com/kyc-mobile.html#t=<handoff token>",
                  "expiresIn": 900
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "404": {
            "$ref": "#/components/responses/VerificationNotFound"
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "string",
                    "description": "Customer phone number in international format, such as `+254712345678`, or email address. Use the `customer_id` recorded when the verification started.",
                    "example": "+254712345678"
                  },
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/handoff": {
      "get": {
        "tags": [
          "Phone handoff"
        ],
        "operationId": "getHandoffStatus",
        "summary": "Read handoff status (phone page)",
        "description": "The hosted phone page uses the handoff token to read the verification status and select the next capture screen.",
        "security": [
          {
            "handoffToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "The phone page gets the current state of the verification."
          },
          "401": {
            "description": "You read the verification result. If capture is still needed, your backend requests a fresh handoff for another attempt."
          }
        }
      }
    },
    "/kyc/handoff/document": {
      "post": {
        "tags": [
          "Phone handoff"
        ],
        "operationId": "handoffSubmitDocument",
        "summary": "Submit the ID (phone page)",
        "description": "The hosted phone page submits the captured image array using its handoff token and receives the document-processing result.",
        "security": [
          {
            "handoffToken": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "images": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "images": [
                  "/9j/4AAQSkZJRg…"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification verdict."
          },
          "401": {
            "description": "Your handoff token expired: create a new handoff."
          }
        }
      }
    },
    "/kyc/handoff/liveness-session": {
      "post": {
        "tags": [
          "Phone handoff"
        ],
        "operationId": "handoffLivenessSession",
        "summary": "Open a liveness session (phone page)",
        "security": [
          {
            "handoffToken": []
          }
        ],
        "responses": {
          "200": {
            "description": "Scoped session: `url` and `token`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LivenessSession"
                }
              }
            }
          },
          "401": {
            "description": "Your handoff token expired: create a new handoff."
          }
        },
        "description": "The hosted phone page uses the handoff token to request a liveness session for face capture."
      }
    },
    "/kyc/handoff/face": {
      "post": {
        "tags": [
          "Phone handoff"
        ],
        "operationId": "handoffSubmitFace",
        "summary": "Submit the liveness result (phone page)",
        "security": [
          {
            "handoffToken": []
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "livenessTransactionId": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "livenessTransactionId": "497f6eca-6276-4993-bfeb-53cbbbba6f08"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification verdict."
          },
          "401": {
            "description": "Your handoff token expired: create a new handoff."
          }
        },
        "description": "After confirmed capture, the hosted phone page submits its transaction ID using the handoff token. Read the resulting verdict on the backend through the customer verification endpoint."
      }
    },
    "/kyc/faceapi/{path}": {
      "post": {
        "tags": [
          "Liveness capture"
        ],
        "operationId": "livenessCapture",
        "summary": "Relay capture traffic",
        "description": "Forward the SDK’s `GET`, `POST` and `PUT` requests to the same sub-path and query string, preserving the raw body and `authorization`, `content-type`, `accept` and `x-client-key` headers. Allow bodies of at least 25 MB. The relay examples use 30-second timeouts, extended to 120 seconds for `/liveness/video`.",
        "security": [
          {
            "livenessToken": []
          }
        ],
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "The sub-path the SDK sets, for example `api/v2/liveness/start`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "x-client-key",
            "in": "header",
            "required": false,
            "description": "Set by the SDK on the final upload. Forward this header through the pass-through.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "contentMediaType": "application/octet-stream"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upstream capture response, forwarded unchanged."
          },
          "401": {
            "description": "`Invalid or expired liveness session`: open a new liveness session."
          },
          "422": {
            "description": "The capture request is missing `x-client-key`. Preserve that header in the pass-through.",
            "content": {
              "application/json": {
                "example": [
                  {
                    "location": [
                      "header",
                      "x-client-key"
                    ],
                    "message": "Field required"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/rp/register": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "registerRp",
        "summary": "Register an application",
        "description": "Registers an application with its name, origin, callback URLs, contact email and requested scopes. The response contains a registration ID and `pending` status. After approval, assigned credentials, granted scopes and signing permissions appear on the dashboard.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RpRegistration"
              },
              "example": {
                "name": "clientX",
                "origin": "https://app.example.com",
                "redirect_uris": [
                  "https://app.example.com/auth/callback"
                ],
                "contact_email": "dev@example.com",
                "requested_scopes": [
                  "signa:sign",
                  "signa-kyc"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registration, pending approval.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "rpId": {
                      "type": "integer"
                    },
                    "status": {
                      "type": "string",
                      "const": "pending"
                    }
                  }
                },
                "example": {
                  "rpId": 42,
                  "status": "pending"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/auth/login-config": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "getLoginConfig",
        "summary": "Get login configuration",
        "description": "Returns the login endpoints, callbacks and identity-provider aliases registered for the supplied client ID. Use an approved provider alias as `kc_idp_hint` in authorization requests.",
        "security": [],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "description": "Your OIDC client id, e.g. `signa-rp-42`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Login configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginConfig"
                },
                "example": {
                  "keycloakUrl": "https://securysign.com/auth",
                  "realm": "signa",
                  "authorizationEndpoint": "https://securysign.com/auth/realms/signa/protocol/openid-connect/auth",
                  "tokenEndpoint": "https://securysign.com/auth/realms/signa/protocol/openid-connect/token",
                  "clientId": "signa-rp-42",
                  "rpName": "clientX",
                  "redirectUris": [
                    "https://app.example.com/auth/callback"
                  ],
                  "providers": [
                    {
                      "alias": "rp-42-clientx-oidc",
                      "displayName": "clientX corporate provider"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`client_id is required`: pass your client ID."
          },
          "403": {
            "description": "`Feature not enabled`: contact SecurySign."
          },
          "404": {
            "description": "`RP not found`: no approved RP has this client ID, so check it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          }
        }
      }
    },
    "/pki/certificates/me": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getMyCertificate",
        "summary": "Get my certificate",
        "description": "Returns the authenticated customer’s current certificate and status. `status: \"none\"` means enrolment is required to obtain a certificate.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Status and the certificate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MyCertificate"
                },
                "example": {
                  "status": "active",
                  "credentialId": "cred_abc123",
                  "certificate": {
                    "certificateId": 123,
                    "commonName": "JANE DOE",
                    "issuer": "Signa Hardware CA",
                    "serialNumberHex": "4F2A…",
                    "validFrom": "2026-09-01 10:00:00",
                    "validUntil": "2027-09-01 10:00:00",
                    "caSource": "platform",
                    "certificatePem": "-----BEGIN CERTIFICATE-----…"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/pki/certificate/{id}": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getCertificate",
        "summary": "Get a certificate",
        "description": "Retrieves a certificate’s metadata and PEM value by certificate ID. The access token must belong to the certificate owner.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CertificateId"
          }
        ],
        "responses": {
          "200": {
            "description": "Certificate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Certificate"
                },
                "example": {
                  "id": 123,
                  "serialNumber": "4F2A…",
                  "certificatePem": "-----BEGIN CERTIFICATE-----…",
                  "publicKeyPem": "-----BEGIN PUBLIC KEY-----…",
                  "issuedAt": "2026-09-01 10:00:00",
                  "expiresAt": "2027-09-01 10:00:00",
                  "caSource": "platform",
                  "status": "active"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/pki/certificate/{id}/chain": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getCertificateChain",
        "summary": "Get a certificate's chain",
        "description": "Retrieves the certificate chain using the certificate ID and owner’s access token. The `chain` array contains PEM certificates ordered from `end-entity` to `root`.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CertificateId"
          }
        ],
        "responses": {
          "200": {
            "description": "Chain.",
            "content": {
              "application/json": {
                "example": {
                  "certificateId": 123,
                  "caSource": "platform",
                  "chain": [
                    {
                      "type": "end-entity",
                      "serialNumber": "…",
                      "certificatePem": "-----BEGIN CERTIFICATE-----…",
                      "issuedAt": "2026-09-01 10:00:00",
                      "expiresAt": "2027-09-01 10:00:00",
                      "status": "active"
                    },
                    {
                      "type": "root",
                      "name": "Signa Hardware CA",
                      "certificatePem": "-----BEGIN CERTIFICATE-----…"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`Forbidden`: use the access token of the certificate's owner."
          },
          "404": {
            "description": "`Certificate not found`: check the certificate ID."
          }
        }
      }
    },
    "/pki/certificates/{id}/renew": {
      "post": {
        "tags": [
          "Certificates"
        ],
        "operationId": "renewCertificate",
        "summary": "Renew a certificate",
        "description": "Requests early renewal using the same signing key, authenticated with the certificate owner’s token. Generate an `idempotencyKey` for the attempt and reuse it for retries. Automatic renewal is scheduled at 90, 30 and 7 days before expiry.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CertificateId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "idempotencyKey": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "idempotencyKey": "renew-123-2026-09-26"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Renewal request."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/pki/certificates/{id}/revoke/challenge": {
      "post": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getRevocationChallenge",
        "summary": "Get a revocation challenge",
        "description": "Creates a single-use revocation challenge valid for five minutes, using the certificate ID and owner’s token. Ask the owner’s registered passkey to sign this challenge with navigator.credentials.get, then submit the assertion to POST /pki/certificates/{id}/revoke.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CertificateId"
          }
        ],
        "responses": {
          "200": {
            "description": "Challenge.",
            "content": {
              "application/json": {
                "example": {
                  "challenge": "q3Zt9uX1b8Kc…",
                  "expiresIn": 300
                }
              }
            }
          },
          "403": {
            "description": "`Forbidden`: use the access token of the certificate's owner."
          },
          "404": {
            "description": "`Certificate not found`: check the certificate ID."
          }
        }
      }
    },
    "/pki/certificates/{id}/revoke": {
      "post": {
        "tags": [
          "Certificates"
        ],
        "operationId": "revokeCertificate",
        "summary": "Revoke a certificate",
        "description": "Submit the owner’s assertion from navigator.credentials.get, revocation reason and access token. SecurySign verifies ownership and passkey approval, immediately revokes the certificate and returns `status: \"revoked\"` with `revokedAt`. Read its status through `/pki/certificate/{id}` or `/pki/ocsp`.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/CertificateId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "credentialId",
                  "assertion"
                ],
                "properties": {
                  "credentialId": {
                    "type": "string"
                  },
                  "assertion": {
                    "$ref": "#/components/schemas/WebAuthnAssertion"
                  },
                  "reason": {
                    "type": "string",
                    "enum": [
                      "keyCompromise",
                      "cessationOfOperation",
                      "superseded",
                      "unspecified"
                    ],
                    "default": "unspecified"
                  }
                }
              },
              "example": {
                "credentialId": "cred_abc123",
                "reason": "keyCompromise",
                "assertion": {
                  "id": "cred_abc123",
                  "rawId": "b64url",
                  "type": "public-key",
                  "response": {
                    "authenticatorData": "b64url",
                    "clientDataJSON": "b64url",
                    "signature": "b64url"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Certificate revoked immediately.",
            "content": {
              "application/json": {
                "example": {
                  "success": true,
                  "certificateId": 123,
                  "status": "revoked",
                  "revokedAt": "2026-10-10 12:00:00"
                }
              }
            }
          },
          "400": {
            "description": "`credentialId and assertion required`: send both."
          },
          "403": {
            "description": "`Assertion verification failed`, or not your certificate"
          },
          "409": {
            "description": "`Revocation challenge missing, expired or already used. …`: request a new challenge. Or `Certificate already revoked`."
          }
        }
      }
    },
    "/pki/ca-cert": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getCaCertificate",
        "summary": "Get the CA certificate",
        "description": "Downloads the CA certificate in PEM format for use as a trust anchor in a signature validator.",
        "security": [],
        "responses": {
          "200": {
            "description": "CA certificate in PEM.",
            "content": {
              "application/x-pem-file": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/pki/crl": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getCrl",
        "summary": "Get the revocation list",
        "security": [],
        "responses": {
          "200": {
            "description": "Current CRL in PEM."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Downloads the signed certificate revocation list (CRL) for checking revocations in a certificate validator."
      }
    },
    "/pki/ocsp": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "checkOcsp",
        "summary": "Check certificate status",
        "description": "Checks certificate status by hexadecimal `serialNumber` or `certId`, returning JSON with `good`, `revoked` or `unknown`. A revoked result includes the time and reason. The same parameters can be submitted as JSON with POST.",
        "security": [],
        "responses": {
          "200": {
            "description": "Status: `good`, `revoked` (with `revocationTime` and `revocationReason`) or `unknown`."
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/protocol/openid-connect/auth": {
      "servers": [
        {
          "url": "https://securysign.com/auth/realms/signa",
          "description": "Production"
        },
        {
          "url": "https://idp.dev.securysign.com/realms/signa",
          "description": "Sandbox"
        }
      ],
      "get": {
        "tags": [
          "Sign-in (OIDC)"
        ],
        "operationId": "oidcAuthorize",
        "summary": "Send your user to sign in",
        "description": "Open this authorization URL with the registered client ID, callback and session state. After sign-in, the callback receives `code` and `state` for the backend exchange.",
        "security": [],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "signa-rp-42"
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "description": "Exact match against your registered URIs.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "code"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "description": "`openid` plus scopes assigned to your client. One unassigned scope fails the whole login with `invalid_scope`.",
            "schema": {
              "type": "string"
            },
            "example": "openid"
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kc_idp_hint",
            "in": "query",
            "description": "`google`, or your enterprise provider's alias, to skip the SecurySign sign-in page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Your user goes to their identity provider, then back to your `redirect_uri`."
          }
        }
      }
    },
    "/protocol/openid-connect/token": {
      "servers": [
        {
          "url": "https://securysign.com/auth/realms/signa",
          "description": "Production"
        },
        {
          "url": "https://idp.dev.securysign.com/realms/signa",
          "description": "Sandbox"
        }
      ],
      "post": {
        "tags": [
          "Sign-in (OIDC)"
        ],
        "operationId": "oidcToken",
        "summary": "Exchange the code",
        "description": "Exchanges an authorization code for user tokens using the registered credentials and exactly the same redirect URI. The response includes expiry fields. Validate the ID token through an OIDC library; identify the account by `sub` and read its verified-email claims.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "code",
                  "redirect_uri",
                  "client_id"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "const": "authorization_code"
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "grant_type": "authorization_code",
                "code": "<code>",
                "redirect_uri": "https://app.example.com/auth/callback",
                "client_id": "signa-rp-42",
                "client_secret": "<oidc-client-secret>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens.",
            "content": {
              "application/json": {
                "example": {
                  "access_token": "eyJhbGciOiJSUzI1NiIs…",
                  "id_token": "eyJhbGciOiJSUzI1NiIs…",
                  "refresh_token": "eyJhbGciOiJIUzUxMiIs…",
                  "expires_in": 300,
                  "token_type": "Bearer"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_grant`: the code was used or expired, or `redirect_uri` does not match. Start sign-in again."
          }
        },
        "x-credential": {
          "name": "OIDC client secret",
          "description": "Send your client ID and OpenID Connect (OIDC) client secret in the form body, from your server only."
        }
      }
    },
    "/authorize": {
      "servers": [
        {
          "url": "https://mimi.ke",
          "description": "MIMI"
        }
      ],
      "get": {
        "tags": [
          "Enrolment (OIDC)"
        ],
        "operationId": "mimiAuthorize",
        "summary": "Start or resume enrolment",
        "description": "Open the MIMI authorization URL with the registered client, PKCE challenge and session parameters. The customer completes the required enrolment steps, reusing any completed steps, before the callback receives a code.",
        "security": [],
        "parameters": [
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "description": "Your MIMI client id (not your SecurySign one).",
            "schema": {
              "type": "string"
            },
            "example": "clientX"
          },
          {
            "name": "redirect_uri",
            "in": "query",
            "required": true,
            "description": "Exact match. Carry your final destination in `state`, not here.",
            "schema": {
              "type": "string",
              "format": "uri"
            }
          },
          {
            "name": "response_type",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "code"
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": true,
            "description": "Send any of `openid`, `email` and `profile`.",
            "schema": {
              "type": "string"
            },
            "example": "openid email profile"
          },
          {
            "name": "state",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "const": "S256"
            }
          },
          {
            "name": "claims",
            "in": "query",
            "description": "Pin the account: `{\"id_token\":{\"sub\":{\"value\":\"<sub>\"}}}`. On a mismatch the customer is asked to switch, and a refusal returns `error=access_denied&error_description=account_mismatch`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "To your `redirect_uri` with `code` and `state`, or `error`"
          },
          "503": {
            "description": "The identity service is unavailable. Contact SecurySign."
          }
        }
      }
    },
    "/token": {
      "servers": [
        {
          "url": "https://mimi.ke",
          "description": "MIMI"
        }
      ],
      "post": {
        "tags": [
          "Enrolment (OIDC)"
        ],
        "operationId": "mimiToken",
        "summary": "Exchange the code",
        "description": "Exchange the code within 60 seconds using the original redirect URI and PKCE verifier. The ID token contains the customer’s unchanged SecurySign `sub`, `email` and `name`. Tokens have a 600-second lifetime.",
        "security": [
          {
            "mimiClient": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "code",
                  "redirect_uri",
                  "code_verifier"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "const": "authorization_code"
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "grant_type": "authorization_code",
                "code": "<code>",
                "redirect_uri": "https://app.example.com/mimi/callback",
                "code_verifier": "<pkce-verifier>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tokens.",
            "content": {
              "application/json": {
                "example": {
                  "access_token": "…",
                  "id_token": "eyJhbGciOiJSUzI1NiIs…",
                  "token_type": "Bearer",
                  "expires_in": 600,
                  "scope": "openid email profile"
                }
              }
            }
          },
          "400": {
            "description": "`invalid_grant`: the code was reused or expired. Send your customer to `/authorize` again."
          }
        }
      }
    },
    "/.well-known/openid-configuration": {
      "servers": [
        {
          "url": "https://mimi.ke",
          "description": "MIMI"
        }
      ],
      "get": {
        "tags": [
          "Enrolment (OIDC)"
        ],
        "operationId": "mimiDiscovery",
        "summary": "Read the discovery document",
        "description": "Provides the MIMI authorization, token and signing-key endpoints for OIDC discovery.",
        "security": [],
        "responses": {
          "200": {
            "description": "MIMI's OpenID Provider metadata.",
            "content": {
              "application/json": {
                "example": {
                  "issuer": "https://mimi.ke",
                  "authorization_endpoint": "https://mimi.ke/authorize",
                  "token_endpoint": "https://mimi.ke/token",
                  "jwks_uri": "https://mimi.ke/jwks",
                  "scopes_supported": [
                    "openid",
                    "email",
                    "profile"
                  ],
                  "code_challenge_methods_supported": [
                    "S256"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/sign/pades/prepare": {
      "post": {
        "tags": [
          "PAdES signing"
        ],
        "operationId": "preparePades",
        "summary": "Prepare a PDF",
        "description": "Prepares a base64 PDF using the customer’s access token. The response contains the byte-range hash, `operationId`, certificate and signing-key identifier. Open the operation in the frame and complete approval and finalization within five minutes. A 20 MB request limit accommodates roughly 15 MB of PDF bytes after base64 encoding.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "pdfBase64"
                ],
                "properties": {
                  "pdfBase64": {
                    "type": "string"
                  },
                  "options": {
                    "type": "object",
                    "properties": {
                      "reason": {
                        "type": "string"
                      },
                      "location": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "example": {
                "pdfBase64": "JVBERi0xLjcK…",
                "options": {
                  "reason": "Contract acceptance",
                  "location": "Nairobi"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Operation, the hash to approve and the signing credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PadesPrepared"
                },
                "example": {
                  "operationId": "op_7c1e0a",
                  "hash": "5f2b9c0000000000000000000000000000000000000000000000000000000000",
                  "certBase64": "MIIC…",
                  "algorithm": "SHA256withECDSA",
                  "credentialID": "signa_prod_1042_0"
                }
              }
            }
          },
          "400": {
            "description": "`pdfBase64 required`: send the PDF as base64.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "pdfBase64 required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "Your request body is over 20 MB: send a smaller PDF."
          }
        }
      }
    },
    "/sign/pades/finalize": {
      "post": {
        "tags": [
          "PAdES signing"
        ],
        "operationId": "finalizePades",
        "summary": "Finalize with the passkey approval",
        "description": "Submit the assertion from `SSC_PADES_APPROVED` using the same customer’s access token. SecurySign checks approval for the prepared hash, performs signing and returns the signed PDF as base64.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PadesFinalizeRequest"
              },
              "example": {
                "operationId": "op_7c1e0a",
                "credentialId": "AbC123…",
                "signatureBase64": "MEUCIQ…",
                "authenticatorData": "SZYN5Y…",
                "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Ii…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completed signature.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "signedPdfBase64": {
                      "type": "string"
                    },
                    "operationId": {
                      "type": "string"
                    },
                    "credentialID": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "signedPdfBase64": "JVBERi0xLjcK…",
                  "operationId": "op_7c1e0a",
                  "credentialID": "signa_prod_1042_0"
                }
              }
            }
          },
          "400": {
            "description": "An assertion field is missing: send `signatureBase64`, `authenticatorData` and `clientDataJSON`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Approve the signature with your passkey: send signatureBase64, authenticatorData and clientDataJSON from the assertion over the prepared hash."
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Passkey signature verification failed`, `Challenge mismatch — dynamic linking failed`, `Origin mismatch — possible phishing attack`, `This passkey is not registered to your account…`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Passkey signature verification failed"
                }
              }
            }
          },
          "404": {
            "description": "`Operation not found or access denied`: it was already finalized or belongs to another user. Prepare again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Operation not found or access denied"
                }
              }
            }
          },
          "410": {
            "description": "You prepared more than 5 minutes ago: prepare again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This signing operation expired. Prepare the PDF again and approve within 5 minutes."
                }
              }
            }
          }
        }
      }
    },
    "/pki/encryption-key": {
      "get": {
        "tags": [
          "Encryption"
        ],
        "operationId": "getEncryptionKey",
        "summary": "Get the user's encryption key",
        "description": "Returns `keyId` and the authenticated customer’s RSA public key as a JSON Web Key. The key is provisioned on the first request.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Public key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EncryptionKey"
                },
                "example": {
                  "keyId": "user_enc_1042",
                  "jwk": {
                    "kty": "RSA",
                    "n": "…",
                    "e": "AQAB",
                    "alg": "RSA-OAEP-256",
                    "use": "enc",
                    "key_ops": [
                      "encrypt",
                      "wrapKey"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SecurySign could not create the key: retry.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Encryption key not found. Please re-register."
                }
              }
            }
          }
        }
      }
    },
    "/encrypt/client": {
      "post": {
        "tags": [
          "Encryption"
        ],
        "operationId": "storeEncryptedDocument",
        "summary": "Store an encrypted document",
        "description": "Stores ciphertext encrypted on the device. For `symmetric`, send `recipientKeyId`, `cipherTextBase64`, `wrappedKeyBase64` and `ivBase64`. For `asymmetric`, send the key ID and ciphertext. The response contains the stored document ID.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "recipientKeyId",
                  "cipherTextBase64"
                ],
                "properties": {
                  "recipientKeyId": {
                    "type": "string"
                  },
                  "cipherTextBase64": {
                    "type": "string"
                  },
                  "wrappedKeyBase64": {
                    "type": "string"
                  },
                  "ivBase64": {
                    "type": "string"
                  },
                  "documentName": {
                    "type": "string"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "symmetric",
                      "asymmetric"
                    ],
                    "default": "symmetric"
                  }
                }
              },
              "example": {
                "recipientKeyId": "user_enc_1042",
                "cipherTextBase64": "…",
                "wrappedKeyBase64": "…",
                "ivBase64": "…",
                "documentName": "Contract.pdf",
                "mode": "symmetric"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored document's ID.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": 381,
                  "recipientKeyId": "user_enc_1042",
                  "documentName": "Contract.pdf",
                  "status": "stored"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/encrypt/list": {
      "get": {
        "tags": [
          "Encryption"
        ],
        "operationId": "listEncryptedDocuments",
        "summary": "List the user's documents",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "User's documents, without ciphertext.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EncryptedDocument"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "description": "Lists encrypted documents belonging to the authenticated customer. Each record includes its encryption `mode` for selecting the decryption flow."
      }
    },
    "/encrypt/{id}": {
      "get": {
        "tags": [
          "Encryption"
        ],
        "operationId": "getEncryptedDocument",
        "summary": "Get one document",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "Supply the `mode` returned by the document list to select the stored record.",
            "schema": {
              "type": "string",
              "enum": [
                "prf",
                "symmetric",
                "asymmetric"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document with its ciphertext.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EncryptedDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "The ID is wrong, or the document belongs to another user: send the ID of one of the signed-in user's documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Document not found"
                }
              }
            }
          },
          "409": {
            "description": "Two stored documents share this ID. Add `?mode=` using the mode returned by the document list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Two of your documents have this id. Add ?mode= with the mode from the document list (prf, symmetric or asymmetric)."
                }
              }
            }
          },
          "400": {
            "description": "Invalid mode. Use `prf`, `symmetric` or `asymmetric`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "mode must be prf, symmetric or asymmetric"
                }
              }
            }
          }
        },
        "description": "Retrieves a stored document by ID with its owner’s access token. The response contains `encrypted_document`, `encrypted_aes_key` and `iv` for decryption."
      },
      "delete": {
        "tags": [
          "Encryption"
        ],
        "operationId": "deleteEncryptedDocument",
        "summary": "Delete one document",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "description": "Supply the `mode` returned by the document list to select the stored record.",
            "schema": {
              "type": "string",
              "enum": [
                "prf",
                "symmetric",
                "asymmetric"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Document with its ciphertext.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "status": "deleted",
                  "id": 381
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "The ID is wrong, or the document belongs to another user: send the ID of one of the signed-in user's documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Document not found or access denied"
                }
              }
            }
          },
          "409": {
            "description": "Two stored documents share this ID. Add `?mode=` using the mode returned by the document list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Two of your documents have this id. Add ?mode= with the mode from the document list (prf, symmetric or asymmetric)."
                }
              }
            }
          },
          "400": {
            "description": "Invalid mode. Use `prf`, `symmetric` or `asymmetric`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "mode must be prf, symmetric or asymmetric"
                }
              }
            }
          }
        },
        "description": "Removes the stored encrypted copy identified by the document ID, authenticated with its owner’s token."
      }
    },
    "/decrypt": {
      "post": {
        "tags": [
          "Encryption"
        ],
        "operationId": "unwrapKey",
        "summary": "Unwrap an AES key",
        "description": "Decrypts a wrapped AES key using the key owner’s access token, `keyId` and matching wrapping mode. The returned `aesKey` is base64-encoded for decryption on the device. Limited to 30 requests per minute per user.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "encryptedAesKeyBase64",
                  "keyId"
                ],
                "properties": {
                  "encryptedAesKeyBase64": {
                    "type": "string"
                  },
                  "keyId": {
                    "type": "string",
                    "description": "Send your user's own key ID, `user_enc_<user id>`."
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "OAEP_SHA256",
                      "PKCS1"
                    ],
                    "default": "PKCS1",
                    "description": "Send `OAEP_SHA256` for keys wrapped with RSA-OAEP SHA-256."
                  }
                }
              },
              "example": {
                "encryptedAesKeyBase64": "…",
                "keyId": "user_enc_1042",
                "mode": "OAEP_SHA256"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Raw AES key, base64.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "aesKey": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`keyId` is not your user's own key: send `user_enc_<user id>`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "You can decrypt only with your own encryption key. Use the keyId from GET /pki/encryption-key."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "`Decryption failed: send the keyId you encrypted to, and mode OAEP_SHA256 for data wrapped with RSA-OAEP SHA-256`: send the `keyId` you wrapped the key with, and `mode: \"OAEP_SHA256\"`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Decryption failed"
                }
              }
            }
          }
        }
      }
    },
    "/decrypt/asymmetric": {
      "post": {
        "tags": [
          "Encryption"
        ],
        "operationId": "decryptSmall",
        "summary": "Decrypt a small payload",
        "description": "Decrypts an RSA ciphertext whose original payload is up to 446 bytes, using the customer’s `keyId` and wrapping mode. The response contains base64-encoded `plainText`. Limited to 20 requests per minute per user.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cipherTextBase64",
                  "keyId"
                ],
                "properties": {
                  "cipherTextBase64": {
                    "type": "string"
                  },
                  "keyId": {
                    "type": "string"
                  },
                  "mode": {
                    "type": "string",
                    "default": "OAEP_SHA256"
                  }
                }
              },
              "example": {
                "cipherTextBase64": "…",
                "keyId": "user_enc_1042",
                "mode": "OAEP_SHA256"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plaintext, base64.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plainText": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`keyId` is not your user's own key: send `user_enc_<user id>`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "You can decrypt only with your own encryption key. Use the keyId from GET /pki/encryption-key."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/pki/certificate/me/pem": {
      "get": {
        "tags": [
          "Certificates"
        ],
        "operationId": "getMyCertificatePem",
        "summary": "Download my certificate (PEM)",
        "description": "Downloads the signer’s certificate in PEM format with their access token. The `signa_certificate_url` claim provides this URL. Save the certificate with the signature for later verification.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "PEM file.",
            "content": {
              "application/x-pem-file": {
                "schema": {
                  "type": "string"
                },
                "example": "-----BEGIN CERTIFICATE-----\n…"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Your user has no active certificate: send them through enrolment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "No active certificate found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/me": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listMyRps",
        "summary": "List my RPs and SSC secrets",
        "description": "Lists approved RPs owned by the authenticated contact account. Each result includes the `ssc_secret` used for signing-token requests.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Approved RPs.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                },
                "example": [
                  {
                    "id": 42,
                    "name": "clientX",
                    "keycloak_client_id": "signa-rp-42",
                    "status": "approved",
                    "max_loa": "LOA-2",
                    "ssc_secret": "3f9a…"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Your token carries no email: sign in with an account that has one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "User email not available"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/rp/rotate-secret": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "rotateClientSecret",
        "summary": "Rotate the OIDC client secret",
        "description": "Rotates the OIDC secret for an approved RP, authenticated as its contact account. The response contains the replacement secret and the old OIDC secret is invalidated. The SSC secret is separate and remains assigned to the RP.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rpId"
                ],
                "properties": {
                  "rpId": {
                    "type": "integer"
                  }
                }
              },
              "example": {
                "rpId": 42
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New secret. Store it now; it is shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "clientId": "signa-rp-42",
                  "clientSecret": "Xk3…"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      }
    },
    "/rp/request-scope-change": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "requestScopeChange",
        "summary": "Request scopes",
        "description": "Requests a replacement scope set for an approved RP using the contact account’s token. Send the complete desired set, which replaces the current grant after approval.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "rpId": 42,
                "requestedScopes": [
                  "signa:sign",
                  "signa-kyc"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request, queued for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangeRequested"
                },
                "example": {
                  "status": "requested",
                  "rpId": 42,
                  "requestId": 7
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/scope-change-requests/{rpId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listScopeChangeRequests",
        "summary": "List scope requests",
        "description": "Lists scope-change requests and their review status for the supplied RP ID. Authenticate as the RP contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      }
    },
    "/rp/request-redirect-uri-change": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "requestRedirectUriChange",
        "summary": "Request redirect URIs",
        "description": "Requests a replacement callback list for an approved RP using the contact account’s token. Send all callbacks that should remain registered, including existing ones. Approval replaces the previous list.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "rpId": 42,
                "redirectUris": [
                  "https://app.example.com/auth/callback"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request, queued for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangeRequested"
                },
                "example": {
                  "status": "requested",
                  "rpId": 42,
                  "requestId": 7
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/redirect-uri-change-requests/{rpId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listRedirectUriChangeRequests",
        "summary": "List redirect URI requests",
        "description": "Lists callback-change requests, statuses and `review_notes` for an RP ID. Authenticate as the RP contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      }
    },
    "/rp/request-loa-change": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "requestLoaChange",
        "summary": "Request a maximum LOA",
        "description": "Requests a higher maximum assurance level for an approved RP using its contact account’s token. LOA-4 also requires RP verification.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "rpId": 42,
                "requestedLoa": "LOA-4"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request, queued for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangeRequested"
                },
                "example": {
                  "status": "requested",
                  "rpId": 42,
                  "requestId": 7
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/loa-change-requests/{rpId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listLoaChangeRequests",
        "summary": "List LOA requests",
        "description": "Lists assurance-level requests and `review_notes` for an RP ID. Authenticate as the RP contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      }
    },
    "/rp/request-iframe-whitelist": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "requestSigningOrigin",
        "summary": "Request a signing origin",
        "description": "Requests approval for an additional embedding origin on an approved RP. Authenticate as the contact account. The signing frame accepts the origin once approved.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "rpId": 42,
                "origin": "https://portal.example.com",
                "name": "Customer portal"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Request, queued for review.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChangeRequested"
                },
                "example": {
                  "status": "requested",
                  "rpId": 42,
                  "requestId": 7
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "You already have a pending request for this origin, or your RP is not approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "A pending request already exists for this origin"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/iframe-whitelist/{rpId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listSigningOrigins",
        "summary": "List signing origin requests",
        "description": "Lists signing-origin requests and their status for an RP ID, using its contact account’s token. An approved active origin has `is_active` set.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      }
    },
    "/rp/iframe-whitelist-request/{requestId}": {
      "delete": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "withdrawSigningOriginRequest",
        "summary": "Withdraw a signing origin request",
        "description": "Withdraws a pending signing-origin request by ID. Authenticate as the RP contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Withdrawn request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "status": "withdrawn",
                  "requestId": 7
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The request belongs to another RP: send the ID of one of your RP's requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this request"
                }
              }
            }
          },
          "404": {
            "description": "The request ID is wrong: check it in the list of your requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Request not found"
                }
              }
            }
          },
          "409": {
            "description": "SecurySign has already reviewed it: submit a new request instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only pending requests can be withdrawn"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/rate-limits/{clientId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "getRateLimits",
        "summary": "Read allowance and usage",
        "description": "Returns the signing allowance and current daily and minute usage for an RP client ID. Authenticate as its contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "signa-rp-42"
          }
        ],
        "responses": {
          "200": {
            "description": "Allowance and usage.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "exists": true,
                  "maxSignaturesDay": 100,
                  "maxBatchSize": 10,
                  "maxPerMinute": 60,
                  "dayUsed": 12,
                  "dayResetsAt": "2026-09-29 00:00:00",
                  "minuteUsed": 1,
                  "minuteResetsAt": "2026-09-28 10:15:00"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not your client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this client"
                }
              }
            }
          }
        }
      }
    },
    "/rp/usage/{clientId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "getUsage",
        "summary": "Read usage",
        "description": "Returns consumption, type and assurance-level breakdowns, daily trends and totals for an RP client ID. Authenticate as its contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "signa-rp-42"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "clientId": "signa-rp-42",
                  "today": {
                    "used": 12,
                    "allowance": 100,
                    "remaining": 88,
                    "percent": 12,
                    "resetsAt": "2026-09-29T00:00:00+00:00"
                  },
                  "rateLimit": {
                    "perMinute": 60,
                    "usedThisMinute": 1
                  },
                  "breakdown": {
                    "byType": {
                      "single": 10,
                      "batch": 2
                    },
                    "byLoa": {
                      "LOA-2": 12
                    }
                  },
                  "trend": [
                    {
                      "date": "2026-09-28",
                      "count": 12
                    }
                  ],
                  "totals": {
                    "last30Days": 214,
                    "allTime": 1380
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not your client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this client"
                }
              }
            }
          }
        }
      }
    },
    "/rp/usage/{clientId}/export": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "exportUsage",
        "summary": "Export usage (CSV)",
        "description": "Downloads usage records as CSV for an RP client ID, authenticated as its contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "signa-rp-42"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV file.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not your client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this client"
                }
              }
            }
          }
        }
      }
    },
    "/rp/integration-types": {
      "get": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "listIntegrationTypes",
        "summary": "List provider types",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Provider types you can add.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "description": "Lists supported identity-provider types for creating a provider configuration."
      }
    },
    "/rp/oauth-providers/discover": {
      "get": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "discoverOidcProvider",
        "summary": "Discover OIDC endpoints",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "issuer",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "https://login.example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Endpoints from the issuer's discovery document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "The issuer is missing or discovery failed: check the issuer URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "issuer is required"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Reads OIDC discovery endpoints from the supplied issuer for use in provider registration."
      }
    },
    "/rp/oauth-providers": {
      "get": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "listIdentityProviders",
        "summary": "List providers",
        "description": "Lists providers for the authenticated contact account’s approved RPs. Supply `rpId` to select one RP.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Providers, or your new pending provider.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/IdentityProvider"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "createIdentityProvider",
        "summary": "Add a provider",
        "description": "Registers a provider for the contact account, returning `pending` status and a broker-callback registration request. OIDC needs the provider’s client ID, secret and issuer or endpoint URLs. SAML needs a metadata URL or entity ID, sign-in URL and certificate. Default scopes are `openid profile email`. `onboarding_mode` selects `off`, `update-profile` or `signa-onboarding`.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rpId",
                  "display_name"
                ],
                "properties": {
                  "rpId": {
                    "type": "integer"
                  },
                  "display_name": {
                    "type": "string"
                  },
                  "provider_type": {
                    "type": "string",
                    "enum": [
                      "oidc",
                      "saml"
                    ],
                    "default": "oidc"
                  },
                  "issuer": {
                    "type": "string"
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "allowed_email_domains": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "onboarding_mode": {
                    "type": "string",
                    "enum": [
                      "off",
                      "update-profile",
                      "signa-onboarding"
                    ]
                  },
                  "saml_metadata_url": {
                    "type": "string"
                  },
                  "saml_entity_id": {
                    "type": "string"
                  },
                  "saml_single_sign_on_service_url": {
                    "type": "string"
                  },
                  "saml_signing_certificate": {
                    "type": "string"
                  },
                  "saml_want_assertions_signed": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "rpId": 42,
                "display_name": "clientX corporate provider",
                "provider_type": "oidc",
                "issuer": "https://login.microsoftonline.com/<tenant>/v2.0",
                "client_id": "…",
                "client_secret": "…",
                "scopes": "openid email profile"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Providers, or your new pending provider.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityProvider"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not your RP, or the RP lacks `signa:integrator`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This RP does not have the Integrator scope. Request it from your dashboard under Request Scope Change, then an admin approves it."
                }
              }
            }
          },
          "404": {
            "description": "`RP not found`: check the RP ID or client ID you sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP not found"
                }
              }
            }
          },
          "409": {
            "description": "`Only approved RPs can be modified`: wait until your RP is approved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only approved RPs can be modified"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/oauth-providers/{id}": {
      "get": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "getIdentityProvider",
        "summary": "Get a provider",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Provider, or `{\"deleted\": true}`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdentityProvider"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The provider belongs to another RP: send the ID of one of your RP's providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "The ID is wrong: check it in the list of your providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "OAuth provider not found"
                }
              }
            }
          }
        },
        "description": "Retrieves a provider’s configuration and review status by ID, authenticated as its RP contact account."
      },
      "delete": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "deleteIdentityProvider",
        "summary": "Delete a pending provider",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Provider, or `{\"deleted\": true}`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "status": "deleted",
                  "id": 5
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "The provider belongs to another RP: send the ID of one of your RP's providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "The ID is wrong: check it in the list of your providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "OAuth provider not found"
                }
              }
            }
          },
          "409": {
            "description": "SecurySign has already reviewed it: submit a new request instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Only pending providers can be deleted by the RP"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Removes a pending provider registration by ID, authenticated as its RP contact account."
      }
    },
    "/rp/oauth-providers/{id}/redirect-uri": {
      "get": {
        "tags": [
          "Identity providers"
        ],
        "operationId": "getBrokerCallback",
        "summary": "Get the broker callback",
        "description": "Returns a provider’s broker callback by ID using the contact account’s token. Register that URL in the provider as its OIDC redirect URI or SAML assertion-consumer-service URL.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Broker callback URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": 5,
                  "redirectUri": "https://securysign.com/auth/realms/signa/broker/clientx-oidc/endpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "The ID is wrong: check it in the list of your providers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "OAuth provider not found"
                }
              }
            }
          }
        }
      }
    },
    "/signature/visible": {
      "get": {
        "tags": [
          "Visible signature"
        ],
        "operationId": "getMyVisibleSignature",
        "summary": "Get my drawn signature",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "User's signature image, PNG base64.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": 12,
                  "certificateId": 123,
                  "imageSha256": "9c1e…",
                  "signatureAlg": "ES256",
                  "sourceType": "drawn",
                  "imagePngBase64": "iVBORw0KGgo…",
                  "createdAt": "2026-09-20 10:00:00",
                  "updatedAt": "2026-09-20 10:00:00"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "description": "Returns the authenticated customer’s saved drawn-signature record."
      }
    },
    "/signature/visible/{userSub}/image": {
      "get": {
        "tags": [
          "Visible signature"
        ],
        "operationId": "getVisibleSignatureImage",
        "summary": "Get the signature image",
        "description": "Downloads the signed PNG using the customer’s access token. The `visible_signature_url` claim provides the image URL.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "userSub",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your signer's `sub`."
          }
        ],
        "responses": {
          "200": {
            "description": "PNG with its embedded signature.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Use the signer's own access token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized"
                }
              }
            }
          },
          "404": {
            "description": "The user has not drawn a signature: ask them to draw one on the Visible signature page of the SecurySign web app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not found"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/signature/visible/verify": {
      "post": {
        "tags": [
          "Visible signature"
        ],
        "operationId": "verifyVisibleSignature",
        "summary": "Verify a signed PNG",
        "description": "Checks the submitted PNG’s embedded signature against the saved certificate.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "signedPngBase64"
                ],
                "properties": {
                  "signedPngBase64": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "signedPngBase64": "iVBORw0KGgo…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result: `valid`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "valid": {
                      "type": "boolean"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "malformed_png",
                        "missing_signature_chunks",
                        "hash_mismatch"
                      ]
                    }
                  }
                },
                "example": {
                  "valid": true
                }
              }
            }
          },
          "400": {
            "description": "The image is missing or invalid: send the signed PNG.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Invalid image"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/signature/visible/public/{token}": {
      "get": {
        "tags": [
          "Visible signature"
        ],
        "operationId": "verifyVisibleSignaturePublic",
        "summary": "Open the public verification page",
        "description": "The QR code on a drawn signature opens this public HTML result, which identifies the signer and issuing CA and reports whether the image is genuine.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The 32-character token in the QR code.",
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{32}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public signature-verification page.",
            "content": {
              "text/html": {}
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v2/plans": {
      "get": {
        "tags": [
          "Plans"
        ],
        "operationId": "listPlans",
        "summary": "List plans",
        "security": [],
        "responses": {
          "200": {
            "description": "Plan catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plans": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plan"
                      }
                    }
                  }
                },
                "example": {
                  "plans": [
                    {
                      "id": "starter",
                      "name": "Starter",
                      "monthlyUsd": 69,
                      "annualUsd": 828,
                      "allowance": {
                        "maxSignaturesPerDay": 100,
                        "maxBatchSize": 2,
                        "maxRequestsPerMinute": 60,
                        "maxLoa": "LOA-2"
                      }
                    },
                    {
                      "id": "business",
                      "name": "Business",
                      "monthlyUsd": 649,
                      "annualUsd": 7788,
                      "allowance": {
                        "maxSignaturesPerDay": 1000,
                        "maxBatchSize": 10,
                        "maxRequestsPerMinute": 300,
                        "maxLoa": "LOA-4"
                      }
                    }
                  ]
                }
              }
            }
          }
        },
        "description": "Lists self-service plans, monthly and annual prices, and the signing allowance provided by each plan."
      }
    },
    "/v2/sign/requests/{requestId}": {
      "get": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "getPendingSigningRequest",
        "summary": "Read a pending signing request",
        "description": "The signing frame uses `requestId` to load a pending request. It displays the document name, hash and assurance level for approval. The response also contains the challenge and passkey information needed by the frame.",
        "security": [],
        "x-credential": {
          "name": "Request ID",
          "description": "The `requestId` or `batchId` is the credential: keep it between your backend and the page that shows the frame."
        },
        "parameters": [
          {
            "name": "requestId",
            "in": "path",
            "required": true,
            "description": "Supply `requestId` from the signing-request creation result.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rpOrigin",
            "in": "query",
            "required": true,
            "description": "Supply your authorised embedding origin, matching the `rpOrigin` recorded when you created the request.",
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "example": "https://app.example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending request.",
            "content": {
              "application/json": {
                "example": {
                  "requestId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                  "documentName": "Contract.pdf",
                  "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                  "loa": "LOA-2",
                  "challenge": "b64-webauthn-challenge",
                  "credentialId": "cred_abc123",
                  "webauthnRpId": null
                }
              }
            }
          },
          "400": {
            "description": "`rpOrigin required: …`: add `rpOrigin` to the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rpOrigin required: send the origin of the page that embeds the signing frame."
                }
              }
            }
          },
          "403": {
            "description": "`rpOrigin does not match …` or `RP origin not authorized: …`: open the frame on the request's origin, and authorise that origin on your RP dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP origin not authorized: https://app.example.com. Authorize it in your RP dashboard under Authorized signing origins."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "description": "`Signing request not found. …`: check `requestId`, or create a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Signing request not found. Create a new one with POST /v2/sign/single."
                }
              }
            }
          },
          "409": {
            "description": "`This signing request is already signed. …`: create a new request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This signing request is already signed. Create a new one with POST /v2/sign/single."
                }
              }
            }
          }
        }
      }
    },
    "/v2/sign/batch/{batchId}": {
      "get": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "getPendingBatch",
        "summary": "Read a pending batch",
        "description": "The signing frame uses `batchId` to load the document list for a single batch approval.",
        "security": [],
        "x-credential": {
          "name": "Request ID",
          "description": "The `requestId` or `batchId` is the credential: keep it between your backend and the page that shows the frame."
        },
        "parameters": [
          {
            "name": "batchId",
            "in": "path",
            "required": true,
            "description": "Supply `batchId` from the batch-creation result.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "rpOrigin",
            "in": "query",
            "required": true,
            "description": "Supply your authorised embedding origin, matching the `rpOrigin` recorded when you created the request.",
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "example": "https://app.example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending batch.",
            "content": {
              "application/json": {
                "example": {
                  "batchId": "3f9c1a2e-5b7d-4e8a-9c1f-0a2b3c4d5e6f",
                  "documents": [
                    {
                      "id": "doc-1",
                      "hash": "a3f7c2d800000000000000000000000000000000000000000000000000000000"
                    },
                    {
                      "id": "doc-2",
                      "hash": "9b2e41f000000000000000000000000000000000000000000000000000000000"
                    }
                  ],
                  "challenge": "b64-webauthn-challenge",
                  "credentialId": "cred_abc123",
                  "webauthnRpId": null
                }
              }
            }
          },
          "400": {
            "description": "`rpOrigin required: …`: add `rpOrigin` to the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rpOrigin required: send the origin of the page that embeds the signing frame."
                }
              }
            }
          },
          "403": {
            "description": "`rpOrigin does not match …` or `RP origin not authorized: …`: open the frame on the request's origin, and authorise that origin on your RP dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP origin not authorized: https://app.example.com. Authorize it in your RP dashboard under Authorized signing origins."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "404": {
            "description": "`Batch not found. …`: check `batchId`, or create a new batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Batch not found. Create a new one with POST /v2/sign/batch."
                }
              }
            }
          },
          "409": {
            "description": "`This batch is already …`: create a new batch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This batch is already completed. Create a new one with POST /v2/sign/batch."
                }
              }
            }
          }
        }
      }
    },
    "/auth/credentials": {
      "get": {
        "tags": [
          "Hash signing"
        ],
        "operationId": "listPasskeys",
        "summary": "List your user's passkeys",
        "description": "Returns up to five passkeys belonging to the authenticated customer, ordered by recent use. Use the selected `credentialId` when creating a signing request.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "User's passkeys.",
            "content": {
              "application/json": {
                "example": [
                  {
                    "credentialId": "cred_abc123",
                    "authenticatorName": "iCloud Keychain",
                    "isHardwareBacked": false,
                    "userVerified": true,
                    "createdAt": "2026-09-01 10:00:00",
                    "lastAssertedAt": "2026-09-27 08:12:40",
                    "certificateId": 42
                  }
                ]
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v2/sign/pades/{operationId}": {
      "get": {
        "tags": [
          "PAdES signing"
        ],
        "operationId": "getPreparedPdf",
        "summary": "Read a prepared PDF",
        "description": "The signing frame uses `operationId` to retrieve the prepared PDF hash and information for the passkey prompt.",
        "security": [],
        "x-credential": {
          "name": "Operation ID",
          "description": "The `operationId` is the credential: keep it between your backend and the page that shows the frame."
        },
        "parameters": [
          {
            "name": "operationId",
            "in": "path",
            "required": true,
            "description": "Supply `operationId` from the PDF-prepare result.",
            "schema": {
              "type": "string"
            },
            "example": "op_7c1e0a"
          },
          {
            "name": "rpOrigin",
            "in": "query",
            "required": true,
            "description": "Supply the authorised origin of your embedding page.",
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "example": "https://app.example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "Prepared PDF's hash and the passkeys your user can approve with.",
            "content": {
              "application/json": {
                "example": {
                  "operationId": "op_7c1e0a",
                  "hash": "5f2b9c0000000000000000000000000000000000000000000000000000000000",
                  "challenge": "X2uc…",
                  "expiresAt": "2026-09-28T10:05:00+00:00",
                  "webauthnRpId": null,
                  "credentialIds": [
                    "AbC123…"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`rpOrigin required: …`: add `rpOrigin` to the query.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rpOrigin required: send the origin of the page that embeds the signing frame."
                }
              }
            }
          },
          "403": {
            "description": "`RP origin not authorized: …`: authorise the origin on your RP dashboard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "RP origin not authorized: https://app.example.com. Authorize it in your RP dashboard under Authorized signing origins."
                }
              }
            }
          },
          "404": {
            "description": "`PDF signing operation not found. …` or `This user has no passkey yet. …`: prepare the PDF again, or enrol the user.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "PDF signing operation not found. Prepare the PDF again with POST /sign/pades/prepare."
                }
              }
            }
          },
          "409": {
            "description": "`This PDF is already signed. …`: prepare the PDF again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This PDF is already signed. Prepare it again with POST /sign/pades/prepare."
                }
              }
            }
          },
          "410": {
            "description": "`This PDF signing operation expired. …`: prepare the PDF again and approve within 5 minutes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This PDF signing operation expired. Prepare the PDF again and approve within 5 minutes."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rp/mobile-apps/{rpId}": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "listMobileApps",
        "summary": "List your RP's mobile apps",
        "description": "Lists registered native applications for an RP ID, authenticated as the contact account.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "rpId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every app, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "rp_id": {
                        "type": "integer"
                      },
                      "platform": {
                        "type": "string",
                        "enum": [
                          "android",
                          "ios"
                        ]
                      },
                      "app_id": {
                        "type": "string",
                        "description": "Supply your Android package name or iOS bundle identifier."
                      },
                      "team_id": {
                        "type": "string",
                        "nullable": true,
                        "description": "Supply your 10-character Apple Developer Team ID for iOS."
                      },
                      "sha256_fingerprints": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Supply each Android signing certificate’s SHA-256 fingerprint as uppercase hexadecimal with colons."
                      },
                      "link_paths": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "You can supply additional iOS Universal Link paths, such as `/sign/*`, for your association file."
                      },
                      "redirect_uris": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Sign-in redirect URIs for your native client: https on webauthn_rp_id, or your app ID as the scheme. Matched exactly."
                      },
                      "webauthn_rp_id": {
                        "type": "string",
                        "description": "Supply the WebAuthn domain associated with the app’s passkeys."
                      },
                      "label": {
                        "type": "string",
                        "nullable": true
                      },
                      "created_at": {
                        "type": "string"
                      }
                    }
                  }
                },
                "example": [
                  {
                    "id": 7,
                    "rp_id": 42,
                    "platform": "android",
                    "app_id": "com.example.clientx",
                    "team_id": null,
                    "sha256_fingerprints": [
                      "AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89"
                    ],
                    "link_paths": [],
                    "redirect_uris": [
                      "https://example.com/app/callback",
                      "com.example.clientx:/oauth2redirect"
                    ],
                    "webauthn_rp_id": "example.com",
                    "label": "Production",
                    "created_at": "2026-10-01 09:00:00"
                  }
                ]
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          }
        }
      }
    },
    "/rp/mobile-apps": {
      "post": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "createMobileApp",
        "summary": "Register a mobile app",
        "description": "Registers the package or bundle ID, signing identity, WebAuthn domain and callbacks for an approved RP, using its contact account’s token. The HTTPS callback must be on an authorized signing origin for that domain. Registration updates the `<client_id>-native` client and domain association files in the same request.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rpId",
                  "platform",
                  "app_id",
                  "webauthn_rp_id"
                ],
                "properties": {
                  "rpId": {
                    "type": "integer"
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "android",
                      "ios"
                    ]
                  },
                  "app_id": {
                    "type": "string"
                  },
                  "team_id": {
                    "type": "string"
                  },
                  "sha256_fingerprints": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "link_paths": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "redirect_uris": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "webauthn_rp_id": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "rpId": 42,
                "platform": "android",
                "app_id": "com.example.clientx",
                "sha256_fingerprints": [
                  "AB:CD:EF:…:89"
                ],
                "redirect_uris": [
                  "https://example.com/app/callback"
                ],
                "webauthn_rp_id": "example.com",
                "label": "Production"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered app.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "rp_id": {
                      "type": "integer"
                    },
                    "platform": {
                      "type": "string",
                      "enum": [
                        "android",
                        "ios"
                      ]
                    },
                    "app_id": {
                      "type": "string",
                      "description": "Supply your Android package name or iOS bundle identifier."
                    },
                    "team_id": {
                      "type": "string",
                      "nullable": true,
                      "description": "Supply your 10-character Apple Developer Team ID for iOS."
                    },
                    "sha256_fingerprints": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Supply each Android signing certificate’s SHA-256 fingerprint as uppercase hexadecimal with colons."
                    },
                    "link_paths": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "You can supply additional iOS Universal Link paths, such as `/sign/*`, for your association file."
                    },
                    "redirect_uris": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Sign-in redirect URIs for your native client: https on webauthn_rp_id, or your app ID as the scheme. Matched exactly."
                    },
                    "webauthn_rp_id": {
                      "type": "string",
                      "description": "Supply the WebAuthn domain associated with the app’s passkeys."
                    },
                    "label": {
                      "type": "string",
                      "nullable": true
                    },
                    "created_at": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "id": 7,
                  "rp_id": 42,
                  "platform": "android",
                  "app_id": "com.example.clientx",
                  "team_id": null,
                  "sha256_fingerprints": [
                    "AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89:AB:CD:EF:01:23:45:67:89"
                  ],
                  "link_paths": [],
                  "redirect_uris": [
                    "https://example.com/app/callback",
                    "com.example.clientx:/oauth2redirect"
                  ],
                  "webauthn_rp_id": "example.com",
                  "label": "Production",
                  "created_at": "2026-10-01 09:00:00"
                }
              }
            }
          },
          "400": {
            "description": "`Redirect URI \"https://other.example/cb\" must be a path on https://example.com (the app's WebAuthn RP ID), e.g. https://example.com/auth/callback`: fix the field the message names: package name, fingerprint, Team ID, redirect URI or link path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Redirect URI \"https://other.example/cb\" must be a path on https://example.com (the app's WebAuthn RP ID), e.g. https://example.com/auth/callback"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "409": {
            "description": "`This package name is already registered for this RP`: remove the existing app first, or register the other platform.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This package name is already registered for this RP"
                }
              }
            }
          },
          "502": {
            "description": "`Could not update your apps' sign-in client, so nothing was saved. Try again in a minute.`: retry; nothing changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Could not update your apps' sign-in client, so nothing was saved. Try again in a minute."
                }
              }
            }
          }
        }
      }
    },
    "/rp/mobile-apps/{id}": {
      "delete": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "deleteMobileApp",
        "summary": "Remove a mobile app",
        "description": "Removes a registered app by ID using the contact account’s token. Its callbacks are removed from the native client and its entries from the generated association files.",
        "security": [
          {
            "userBearer": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registered application removed.",
            "content": {
              "application/json": {
                "example": {
                  "success": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "`Not authorized for this RP`: sign in as your RP's contact email.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Not authorized for this RP"
                }
              }
            }
          },
          "404": {
            "description": "`Mobile app not found`: check the app ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Mobile app not found"
                }
              }
            }
          }
        }
      }
    },
    "/well-known/{domain}/assetlinks.json": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "wellKnownAssetLinks",
        "summary": "Get assetlinks.json for your domain",
        "description": "Proxy this JSON on the application’s domain at `https://<domain>/.well-known/assetlinks.json`. The Digital Asset Links entries identify registered Android apps belonging to the RP that has the domain as an authorized signing origin.",
        "security": [],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "File as `application/json`.",
            "content": {
              "application/json": {
                "example": [
                  {
                    "relation": [
                      "delegate_permission/common.get_login_creds",
                      "delegate_permission/common.handle_all_urls"
                    ],
                    "target": {
                      "namespace": "android_app",
                      "package_name": "com.example.clientx",
                      "sha256_cert_fingerprints": [
                        "AB:CD:EF:…:89"
                      ]
                    }
                  }
                ]
              }
            }
          },
          "404": {
            "description": "`Unknown domain: example.com is not a WebAuthn RP ID of this deployment`: ask support to allow your domain as a WebAuthn RP ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unknown domain: example.com is not a WebAuthn RP ID of this deployment"
                }
              }
            }
          }
        }
      }
    },
    "/well-known/{domain}/apple-app-site-association": {
      "get": {
        "tags": [
          "Relying parties"
        ],
        "operationId": "wellKnownAppleAppSiteAssociation",
        "summary": "Get apple-app-site-association for your domain",
        "description": "Proxy this JSON on the application’s domain at `https://<domain>/.well-known/apple-app-site-association`. It contains registered iOS apps, link paths and HTTPS callbacks for the RP that has the domain as an authorized signing origin.",
        "security": [],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "example.com"
          }
        ],
        "responses": {
          "200": {
            "description": "File as `application/json`.",
            "content": {
              "application/json": {
                "example": {
                  "webcredentials": {
                    "apps": [
                      "ABCDE12345.com.example.clientx"
                    ]
                  },
                  "applinks": {
                    "details": [
                      {
                        "appIDs": [
                          "ABCDE12345.com.example.clientx"
                        ],
                        "components": [
                          {
                            "/": "/app/callback"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "404": {
            "description": "`Unknown domain: example.com is not a WebAuthn RP ID of this deployment`: ask support to allow your domain as a WebAuthn RP ID.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Unknown domain: example.com is not a WebAuthn RP ID of this deployment"
                }
              }
            }
          }
        }
      }
    },
    "/enrolment/request": {
      "post": {
        "tags": [
          "Enrolment (OIDC)"
        ],
        "operationId": "pushEnrolmentRequest",
        "summary": "Push an enrolment request",
        "description": "Creates a `request_uri` for a verified `customer_id`. URL-encode the reference and add it before `#/enrol` in the hosted enrolment link. The first account to open the link within 600 seconds is assigned the latest verified result for that customer and has one hour to finish.",
        "security": [
          {
            "rpBasic": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rp_urn": {
                    "type": [
                      "string",
                      "integer"
                    ],
                    "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
                    "example": "user-42"
                  },
                  "customer_id": {
                    "type": "string",
                    "description": "Supply the customer identifier recorded when you started the verification.",
                    "example": "+254712345678"
                  }
                },
                "required": [
                  "customer_id"
                ]
              },
              "examples": {
                "withoutRpUrn": {
                  "summary": "Without rp_urn",
                  "value": {
                    "customer_id": "+254712345678"
                  }
                },
                "withRpUrn": {
                  "summary": "With rp_urn and customer_id",
                  "value": {
                    "customer_id": "+254712345678",
                    "rp_urn": "user-42"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`request_uri` for the enrolment link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "request_uri": {
                      "type": "string",
                      "description": "Put this on the enrolment link as `request_uri`, URL-encoded."
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Your customer has 600 seconds to open the enrolment link."
                    }
                  }
                },
                "example": {
                  "request_uri": "urn:securysign:request:Xk3fQ9vT2mL8",
                  "expires_in": 600
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidClient"
          },
          "403": {
            "description": "Your RP lacks the `signa-kyc` scope, or the `rp_urn` URN carries another RP's `client_id`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn belongs to another relying party: send an rp_urn your RP set"
                }
              }
            }
          },
          "409": {
            "description": "`rp_urn` does not belong to this `customer_id`: send the `rp_urn` you set for this customer, or omit it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "rp_urn urn:securysign:signa-rp-42:user-42 does not belong to this customer_id: send the rp_urn recorded for this customer, or omit rp_urn"
                }
              }
            }
          },
          "422": {
            "description": "Missing customer identifier. Include `customer_id`, alongside `rp_urn` when using a reference.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "customer_id is required"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "sign.complete": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "signCompleteEvent",
        "summary": "sign.complete",
        "description": "Delivered when a hash-signing request completes: signed for each subscription and unsigned for the request's `callbackUrl`. Verify the signature on subscription deliveries.",
        "parameters": [
          {
            "name": "X-Webhook-Signature",
            "in": "header",
            "description": "`sha256=` + hex HMAC-SHA256 of the raw body with your secret. Absent on `callbackUrl` deliveries.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "event": "sign.complete",
                "timestamp": "2026-09-26T12:00:04+00:00",
                "data": {
                  "requestId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                  "documentHash": "a3f7c2d8e9b14f0c000000000000000000000000000000000000000000000000",
                  "signature": "MEUCIQDxY…",
                  "signedAt": "2026-09-26T12:00:04+00:00"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Answer with any 2xx. SecurySign sends each event once."
          }
        }
      }
    },
    "batch.complete": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "batchCompleteEvent",
        "summary": "batch.complete",
        "description": "Delivered when a batch completes, with the same signing rules as `sign.complete`.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "event": "batch.complete",
                "timestamp": "2026-09-26T12:00:09+00:00",
                "data": {
                  "batchId": "f0e1d2c3-b4a5-4968-8776-655443322110",
                  "signedCount": 1,
                  "failedCount": 1,
                  "results": [
                    {
                      "documentId": "doc-1",
                      "status": "signed",
                      "signature": "MEUCIQDxY…"
                    },
                    {
                      "documentId": "doc-2",
                      "status": "failed",
                      "error": "Invalid document hash"
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Answer with any 2xx."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "rpBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "Your RP's OIDC `client_id` and `client_secret`. Server-side only."
      },
      "userBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Send your signed-in user's access token from the Sign-in (OIDC) token endpoint. A client-credentials (machine) token is rejected."
      },
      "livenessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send the scoped liveness token from Open a liveness session. It works for 600 s and one verification."
      },
      "handoffToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Send the handoff token from Mint a handoff token. It works for 900 s and one verification."
      },
      "mimiClient": {
        "type": "http",
        "scheme": "basic",
        "description": "Your MIMI `client_id` and `client_secret` (`client_secret_basic`)."
      }
    },
    "parameters": {
      "CertificateId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "You correct the missing field or invalid format named in `error`, then resubmit the request.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "clientId, clientSecret, and documentHash required"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Supply a current user access token. For `Missing Authorization header`, add it; for `Invalid or expired token`, refresh or sign in again; for `Token missing subject claim`, ask SecurySign to check the client’s `basic` scope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Missing Authorization header"
            }
          }
        }
      },
      "Forbidden": {
        "description": "You use the message to check the required scope, RP approval, operation ownership or permitted origin.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Insufficient scope"
            }
          }
        }
      },
      "NotFound": {
        "description": "You check the item ID and the user or RP that created it.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Not found"
            }
          }
        }
      },
      "RateLimited": {
        "description": "You wait for the 60-second request window, or the stated daily reset, before retrying.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Rate limit exceeded. Please try again later."
            }
          }
        }
      },
      "InvalidClient": {
        "description": "Supply the approved RP’s client ID and current OIDC secret through HTTP Basic authentication.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "invalid_client"
            }
          }
        }
      },
      "KycNotEnabled": {
        "description": "You request `signa-kyc` for your RP. For `CSRF token validation failed`, you check that the backend supplies its Authorization header.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "This relying party is not enabled for Identity Verification"
            }
          }
        }
      },
      "VerificationNotFound": {
        "description": "You start a verification for this `customer_id` and RP through `POST /kyc/verifications`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "No verification for this customer: start one with POST /kyc/verifications"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Error message identifying the failed field or operation. Read it alongside the HTTP status."
          }
        }
      },
      "Loa": {
        "type": "string",
        "enum": [
          "LOA-0",
          "LOA-1",
          "LOA-2",
          "LOA-4"
        ],
        "description": "Your requested level of assurance. With LOA-2, the customer selects a registered passkey; with LOA-4, the token is assigned to a particular passkey."
      },
      "SigningTokenRequest": {
        "type": "object",
        "required": [
          "clientId",
          "clientSecret",
          "documentHash"
        ],
        "properties": {
          "clientId": {
            "type": "string",
            "description": "Supply the client ID assigned at RP approval, such as `signa-rp-42`."
          },
          "clientSecret": {
            "type": "string",
            "description": "SSC secret from the RP dashboard, supplied by the backend. This is a separate credential from the OIDC client secret."
          },
          "documentHash": {
            "type": "string",
            "description": "You compute the SHA-256 hash of the exact document bytes and supply the complete 64-character hexadecimal digest."
          },
          "loa": {
            "$ref": "#/components/schemas/Loa",
            "default": "LOA-2"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "For LOA-4, you supply the signer’s account email. SecurySign assigns the token to that account’s most recently registered passkey."
          }
        }
      },
      "SigningToken": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "You pass this value as `token` in your signing-frame URL."
          },
          "expiresAt": {
            "type": "integer",
            "description": "The expiry as Unix seconds, five minutes after token issuance."
          },
          "loa": {
            "$ref": "#/components/schemas/Loa"
          },
          "credentialBound": {
            "type": "boolean",
            "description": "`true` when the token is assigned to the LOA-4 passkey."
          }
        }
      },
      "SscChallengeRequest": {
        "type": "object",
        "required": [
          "documentHash",
          "documentName",
          "rpOrigin",
          "mode"
        ],
        "properties": {
          "documentHash": {
            "type": "string"
          },
          "documentName": {
            "type": "string"
          },
          "rpOrigin": {
            "type": "string",
            "format": "uri"
          },
          "mode": {
            "type": "string",
            "enum": [
              "anonymous",
              "registered"
            ]
          },
          "token": {
            "type": "string",
            "description": "Your frame supplies the signing token issued for this document in `registered` mode."
          }
        }
      },
      "SscChallenge": {
        "type": "object",
        "properties": {
          "operationId": {
            "type": "string"
          },
          "challenge": {
            "type": "string",
            "description": "The base64url WebAuthn challenge associated with this operation and document hash."
          },
          "credentialID": {
            "type": [
              "string",
              "null"
            ],
            "description": "For LOA-4, you receive the passkey assigned to this operation."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "rpId": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          }
        }
      },
      "SscFinalizeRequest": {
        "type": "object",
        "required": [
          "operationId",
          "assertion",
          "documentHash"
        ],
        "properties": {
          "operationId": {
            "type": "string"
          },
          "assertion": {
            "$ref": "#/components/schemas/WebAuthnAssertion"
          },
          "documentHash": {
            "type": "string",
            "description": "Send the same hash the challenge was built for."
          }
        }
      },
      "SscSignature": {
        "type": "object",
        "properties": {
          "signatureId": {
            "type": "string"
          },
          "signatureBase64": {
            "type": "string",
            "description": "Your signature: a DER-encoded ECDSA (P-256) signature over the document's SHA-256 hash, base64. Verify it with the signer's certificate."
          },
          "documentHash": {
            "type": "string"
          },
          "userVerified": {
            "type": "boolean"
          },
          "credentialId": {
            "type": "string"
          },
          "timestamp": {
            "type": "integer"
          },
          "mode": {
            "type": "string"
          },
          "levelOfAssurance": {
            "$ref": "#/components/schemas/Loa"
          }
        }
      },
      "WebAuthnAssertion": {
        "type": "object",
        "description": "Your page serialises the `PublicKeyCredential` from `navigator.credentials.get()`, encoding binary fields as base64url.",
        "required": [
          "id",
          "rawId",
          "type",
          "response"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "rawId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "const": "public-key"
          },
          "response": {
            "type": "object",
            "required": [
              "authenticatorData",
              "clientDataJSON",
              "signature"
            ],
            "properties": {
              "authenticatorData": {
                "type": "string"
              },
              "clientDataJSON": {
                "type": "string"
              },
              "signature": {
                "type": "string"
              },
              "userHandle": {
                "type": "string"
              }
            }
          }
        }
      },
      "SigningRequestCreate": {
        "type": "object",
        "required": [
          "documentHash",
          "credentialId"
        ],
        "properties": {
          "documentHash": {
            "type": "string"
          },
          "credentialId": {
            "type": "string",
            "description": "You select the customer’s `credentialId` from `GET /auth/credentials` using that customer’s access token."
          },
          "documentName": {
            "type": "string",
            "description": "Supply the document name shown to the customer in the signing frame."
          },
          "hashAlgorithm": {
            "type": "string",
            "default": "2.16.840.1.101.3.4.2.1",
            "description": "Supply the hash algorithm as an object identifier (OID); the default is `2.16.840.1.101.3.4.2.1` for SHA-256."
          },
          "signatureFormat": {
            "type": "string",
            "default": "PAdES-B-LT",
            "description": "Format label stored with the signing request; hash signing returns a signature of the submitted hash. It does not assemble a PDF signature."
          },
          "loa": {
            "$ref": "#/components/schemas/Loa",
            "default": "LOA-1"
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Supply an HTTPS callback for `sign.complete`. Your backend confirms the notification through an authenticated result."
          },
          "rpOrigin": {
            "type": "string",
            "format": "uri",
            "description": "Supply the origin of the embedding page to bind the request and its approval to that origin."
          }
        }
      },
      "SigningRequestPending": {
        "type": "object",
        "properties": {
          "requestId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "pending_auth"
          },
          "authUrl": {
            "type": "string",
            "description": "Your signing frame sends the assertion to this URL."
          },
          "challenge": {
            "type": "string",
            "description": "Your signing frame supplies this challenge to the customer’s passkey prompt."
          }
        }
      },
      "SigningComplete": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "completed"
          },
          "signature": {
            "type": "string"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "loa": {
            "$ref": "#/components/schemas/Loa"
          }
        }
      },
      "BatchCreate": {
        "type": "object",
        "required": [
          "documents",
          "credentialId"
        ],
        "properties": {
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "hash"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Application-assigned document ID, echoed in the results."
                },
                "hash": {
                  "type": "string"
                },
                "hash_algo": {
                  "type": "string",
                  "default": "2.16.840.1.101.3.4.2.1"
                }
              }
            }
          },
          "credentialId": {
            "type": "string"
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Supply the callback for `batch.complete` and confirm completion through an authenticated result."
          }
        }
      },
      "BatchPending": {
        "type": "object",
        "properties": {
          "batchId": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "const": "pending_auth"
          },
          "documentCount": {
            "type": "integer"
          },
          "authUrl": {
            "type": "string"
          },
          "challenge": {
            "type": "string"
          }
        }
      },
      "BatchComplete": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "completed",
              "partial",
              "failed"
            ]
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "documentId": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "signed",
                    "failed"
                  ]
                },
                "signature": {
                  "type": "string"
                },
                "error": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "WebhookCreate": {
        "type": "object",
        "required": [
          "url",
          "events",
          "secret"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Your application supplies the publicly reachable HTTPS handler for deliveries."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "sign.complete",
                "batch.complete"
              ]
            }
          },
          "secret": {
            "type": "string",
            "description": "You generate this random secret and configure the same value in your webhook verifier."
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "sign.complete",
              "batch.complete"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "data": {
            "type": "object"
          }
        }
      },
      "VerificationStart": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "Send your customer's phone number in international format, such as `+254712345678`, or their email address. SecurySign checks a phone number against its country's numbering plan, so include the `+` and the country code; spaces, dashes, dots, brackets and a leading `00` are fine. SecurySign stores phone numbers in E.164 format and email addresses in lower case, so equivalent spellings reach the same customer. Every later call names the customer with the same `customer_id`. Each `customer_id` is one customer. If omitted, SecurySign uses `phone_number` as the `customer_id`; send it explicitly when you include `rp_urn`."
          },
          "phone_number": {
            "type": "string",
            "description": "Send your customer's phone number in international format, such as `+254712345678`; SecurySign stores it in E.164 format. It is required even when `customer_id` is the same phone number. A phone number belongs to one customer only, and a customer keeps the phone number they were first registered with.",
            "example": "+254712345678"
          },
          "rp_urn": {
            "type": [
              "string",
              "integer"
            ],
            "maxLength": 128,
            "description": "Optional. The ID your own system uses for the customer, for example a user ID, a UUID or `urn:yourapp:user:42`: 1 to 128 printable ASCII characters, no spaces, case-sensitive. Requires an explicit `customer_id`. SecurySign returns it as `urn:securysign:<client_id>:<id>`; only your RP can use it. Each `rp_urn` maps to a single customer at your RP, and each customer holds a single `rp_urn`.",
            "example": "user-42"
          }
        },
        "required": [
          "customer_id",
          "phone_number"
        ]
      },
      "VerificationHandle": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The normalised customer identifier: an E.164 phone number or lowercase email address."
          },
          "phone_number": {
            "type": "string",
            "description": "The customer’s registered phone number in E.164 format."
          },
          "status": {
            "$ref": "#/components/schemas/VerificationStatus"
          },
          "created": {
            "type": "boolean",
            "description": "`false` when you got an existing verification."
          },
          "enrolled": {
            "type": "boolean",
            "description": "With existing-identity comparison enabled, you receive `true` when SecurySign already holds a verified identity in the configured lookup scope. You use the verdict’s `matches` to compare the submitted attributes."
          },
          "rp_urn": {
            "type": "string",
            "description": "Recorded application reference in the form `urn:securysign:<client_id>:<id>`, when assigned."
          }
        }
      },
      "VerificationStatus": {
        "type": "string",
        "enum": [
          "pending",
          "document_verified",
          "verified",
          "needs_review",
          "failed"
        ]
      },
      "Verdict": {
        "type": "object",
        "description": "Fields are populated as document and face processing complete. An unpopulated value is `null`.",
        "required": [
          "customer_id",
          "status",
          "reasons"
        ],
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The normalised customer identifier: an E.164 phone number or lowercase email address."
          },
          "status": {
            "$ref": "#/components/schemas/VerificationStatus"
          },
          "verified_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The surname and given names, or full name, read from the supplied document."
          },
          "doc_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "The document type, such as `National ID` or `Passport`."
          },
          "doc_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The number of the document itself. On a Kenyan national ID card this is the card's serial number, which changes when the card is replaced; read the person's ID number from `personal_number`."
          },
          "personal_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The holder’s personal number when extracted: the national ID number on a Kenyan identity card or the personal number printed on a passport. The value is `null` when unavailable."
          },
          "doc_expiry": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extracted document date, in the format returned by document processing; null when unavailable."
          },
          "nationality": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country value extracted from the document, in the representation returned by document processing; null when unavailable."
          },
          "surname": {
            "type": [
              "string",
              "null"
            ],
            "description": "The surname read from the document."
          },
          "given_names": {
            "type": [
              "string",
              "null"
            ],
            "description": "The given names read from the document."
          },
          "date_of_birth": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extracted document date, in the format returned by document processing; null when unavailable."
          },
          "sex": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sex value extracted from the document, such as `M` or `F`; null when unavailable."
          },
          "issuing_state": {
            "type": [
              "string",
              "null"
            ],
            "description": "Country value extracted from the document, in the representation returned by document processing; null when unavailable."
          },
          "date_of_issue": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extracted document date, in the format returned by document processing; null when unavailable."
          },
          "face_match_score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1
          },
          "liveness_score": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "document_not_authentic",
                "liveness_failed",
                "face_mismatch",
                "face_match_borderline",
                "identity_mismatch"
              ]
            }
          },
          "selfie": {
            "type": [
              "object",
              "null"
            ],
            "description": "With `return_selfie`, you receive the retained capture frame for a verified customer when available. `null` identifies an unfinished or unsuccessful verification, or an unavailable retained image.",
            "properties": {
              "content_type": {
                "type": "string",
                "example": "image/png"
              },
              "data": {
                "type": "string",
                "description": "The retained image bytes as base64."
              }
            }
          },
          "enrolled": {
            "type": "boolean",
            "description": "`true` when SecurySign already held a verified identity for this customer when you started the verification."
          },
          "matches": {
            "type": "object",
            "description": "How this verification compares with the identity SecurySign already holds for the customer. Each value is `true` (matches), `false` (differs) or `null` (nothing on file, or the step has not run yet). Document values are populated after document processing and `face` after face processing. All are `null` when `enrolled` is `false`.",
            "properties": {
              "face": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "full_name": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "surname": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "given_names": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "document_number": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "personal_number": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "document_type": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "nationality": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "issuing_state": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "date_of_birth": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "date_of_expiry": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "date_of_issue": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "sex": {
                "type": [
                  "boolean",
                  "null"
                ]
              }
            }
          },
          "rp_urn": {
            "type": "string",
            "description": "Recorded application reference in the form `urn:securysign:<client_id>:<id>`, when assigned."
          }
        }
      },
      "LivenessSession": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "const": "/api/kyc/faceapi"
          },
          "token": {
            "type": "string"
          },
          "expiresIn": {
            "type": "integer",
            "const": 600
          }
        }
      },
      "CompareResult": {
        "type": "object",
        "properties": {
          "match": {
            "type": "boolean",
            "description": "`true` when `similarity` is at least the returned `threshold`."
          },
          "similarity": {
            "type": "number"
          },
          "threshold": {
            "type": "number"
          }
        }
      },
      "RpRegistration": {
        "type": "object",
        "required": [
          "name",
          "origin",
          "redirect_uris",
          "contact_email"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "origin": {
            "type": "string",
            "format": "uri",
            "description": "The HTTPS origin of your application. Approval authorises it as a signing origin."
          },
          "redirect_uris": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "contact_email": {
            "type": "string",
            "format": "email"
          },
          "contact_phone": {
            "type": "string"
          },
          "logo_url": {
            "type": "string",
            "format": "uri"
          },
          "business_registration_number": {
            "type": "string"
          },
          "requested_scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "signa:sign",
                "signa:ra",
                "signa:reseller",
                "signa:integrator",
                "signa-enrolment",
                "signa-entitlement-required",
                "signa-visible-signature",
                "signa-certificate",
                "signa-kyc"
              ]
            }
          }
        }
      },
      "LoginConfig": {
        "type": "object",
        "properties": {
          "keycloakUrl": {
            "type": "string"
          },
          "realm": {
            "type": "string"
          },
          "authorizationEndpoint": {
            "type": "string"
          },
          "tokenEndpoint": {
            "type": "string"
          },
          "clientId": {
            "type": "string"
          },
          "rpName": {
            "type": "string"
          },
          "redirectUris": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "alias": {
                  "type": "string"
                },
                "displayName": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "MyCertificate": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "active",
              "none"
            ]
          },
          "credentialId": {
            "type": "string"
          },
          "certificate": {
            "type": "object",
            "properties": {
              "certificateId": {
                "type": "integer"
              },
              "commonName": {
                "type": "string"
              },
              "issuer": {
                "type": "string"
              },
              "serialNumberHex": {
                "type": "string"
              },
              "validFrom": {
                "type": "string"
              },
              "validUntil": {
                "type": "string"
              },
              "caSource": {
                "type": "string"
              },
              "certificatePem": {
                "type": "string"
              }
            }
          }
        }
      },
      "Certificate": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "serialNumber": {
            "type": "string"
          },
          "certificatePem": {
            "type": "string"
          },
          "publicKeyPem": {
            "type": "string"
          },
          "issuedAt": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string"
          },
          "caSource": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "revoked"
            ]
          }
        }
      },
      "PadesPrepared": {
        "type": "object",
        "properties": {
          "operationId": {
            "type": "string"
          },
          "hash": {
            "type": "string",
            "description": "The prepared byte-range SHA-256 hash as hexadecimal. Its decoded bytes are the passkey challenge."
          },
          "certBase64": {
            "type": "string"
          },
          "algorithm": {
            "type": "string",
            "const": "SHA256withECDSA"
          },
          "credentialID": {
            "type": "string",
            "description": "The identifier of the customer’s active server-side signing key."
          }
        }
      },
      "PadesFinalizeRequest": {
        "type": "object",
        "required": [
          "operationId",
          "signatureBase64",
          "authenticatorData",
          "clientDataJSON"
        ],
        "properties": {
          "operationId": {
            "type": "string"
          },
          "credentialId": {
            "type": "string",
            "description": "You copy `assertion.id` from the approval event. With an omitted ID, SecurySign tries the customer’s registered passkeys."
          },
          "signatureBase64": {
            "type": "string",
            "description": "Send `assertion.response.signature`, base64 or base64url."
          },
          "authenticatorData": {
            "type": "string",
            "description": "Send `assertion.response.authenticatorData`, base64 or base64url."
          },
          "clientDataJSON": {
            "type": "string",
            "description": "Send `assertion.response.clientDataJSON`, base64 or base64url."
          },
          "credentialID": {
            "type": "string",
            "description": "You can supply `credentialID` from prepare; the default is the customer’s active server-side signing key."
          }
        }
      },
      "EncryptionKey": {
        "type": "object",
        "properties": {
          "keyId": {
            "type": "string",
            "example": "user_enc_1042"
          },
          "jwk": {
            "type": "object",
            "description": "The customer’s RSA public key in JSON Web Key format with `alg: \"RSA-OAEP-256\"`."
          }
        }
      },
      "EncryptedDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "document_name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "symmetric",
              "asymmetric",
              "prf"
            ]
          },
          "recipient_key_id": {
            "type": "string"
          },
          "encrypted_document": {
            "type": "string",
            "description": "The base64 ciphertext when retrieving one stored document."
          },
          "encrypted_aes_key": {
            "type": "string"
          },
          "iv": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "ChangeRequested": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "const": "requested"
          },
          "rpId": {
            "type": "integer"
          },
          "requestId": {
            "type": "integer"
          }
        }
      },
      "IdentityProvider": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "rp_id": {
            "type": "integer"
          },
          "display_name": {
            "type": "string"
          },
          "provider_type": {
            "type": "string",
            "enum": [
              "oidc",
              "saml"
            ]
          },
          "keycloak_alias": {
            "type": "string",
            "description": "Supply this provider alias as `kc_idp_hint` in your authorization URL."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected"
            ]
          }
        }
      },
      "Plan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "monthlyUsd": {
            "type": "number"
          },
          "annualUsd": {
            "type": "number"
          },
          "allowance": {
            "type": "object",
            "properties": {
              "maxSignaturesPerDay": {
                "type": "integer"
              },
              "maxBatchSize": {
                "type": "integer"
              },
              "maxRequestsPerMinute": {
                "type": "integer"
              },
              "maxLoa": {
                "$ref": "#/components/schemas/Loa"
              }
            }
          }
        }
      },
      "CustomerCompareRequest": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "The customer's phone number in international format or email address, under the same rules as when you start a verification."
          },
          "checks": {
            "type": "object",
            "description": "Optional. The details you want to check, as an object of attribute names and values. Send any of the listed attributes; you get one result for each in `matches`.",
            "properties": {
              "full_name": {
                "type": "string"
              },
              "surname": {
                "type": "string"
              },
              "given_names": {
                "type": "string"
              },
              "document_number": {
                "type": "string"
              },
              "personal_number": {
                "type": "string"
              },
              "document_type": {
                "type": "string"
              },
              "nationality": {
                "type": "string"
              },
              "issuing_state": {
                "type": "string"
              },
              "date_of_birth": {
                "type": "string"
              },
              "date_of_expiry": {
                "type": "string"
              },
              "date_of_issue": {
                "type": "string"
              },
              "sex": {
                "type": "string"
              }
            }
          },
          "image": {
            "type": "string",
            "contentEncoding": "base64",
            "description": "Send a base64 photo, without a `data:` prefix, to compare it with the identity's document portrait. Leave it out to check values only."
          },
          "rp_urn": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Optional reference recorded for this `customer_id`, as a plain ID or `urn:securysign:<client_id>:<id>`. The namespace belongs to the authenticated RP; a reference from another RP receives `403`.",
            "example": "user-42"
          }
        },
        "required": [
          "customer_id"
        ]
      },
      "CustomerCompareResult": {
        "type": "object",
        "properties": {
          "customer_id": {
            "type": "string",
            "description": "Your `customer_id`, as SecurySign stores it."
          },
          "enrolled": {
            "type": "boolean",
            "description": "`true` when SecurySign holds a verified identity for this customer."
          },
          "matches": {
            "type": "object",
            "description": "`customer_id`, then one entry per check you sent: `true` (matches), `false` (differs) or `null` (nothing on file). Every value is `null`, and `customer_id` is `false`, when `enrolled` is `false`.",
            "additionalProperties": {
              "type": [
                "boolean",
                "null"
              ]
            }
          },
          "face": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CompareResult"
              },
              {
                "type": "null"
              }
            ],
            "description": "The photo comparison when you sent `image` and the customer is enrolled, otherwise `null`."
          },
          "rp_urn": {
            "type": "string",
            "description": "Recorded application reference in the form `urn:securysign:<client_id>:<id>`, when assigned."
          }
        }
      }
    }
  }
}
