{"openapi":"3.0.3","info":{"title":"Control Orchestrator API","description":"API for orchestrating identity verification and key management workflows","version":"1.0.0"},"servers":[{"url":"https://orchestrator.control.coincover.com","description":"Production server"},{"url":"https://orchestrator.uat-control.coincover.com","description":"Development server"}],"tags":[{"name":"Verification","description":"Start, check, and simulate biometric identity verification."},{"name":"Keys","description":"Generate, assign, and verify cryptographic keys."},{"name":"Backups","description":"Store and recover encrypted backup data."}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"API key for authentication and authorization"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"ID token as Bearer token"}},"schemas":{"IdToken":{"type":"object","description":"ID token payload structure","properties":{"aud":{"type":"string","example":"https://app.wallet.co","description":"Audience - the intended recipient of the token"},"iss":{"type":"string","example":"https://app.wallet.co","description":"Issuer - the entity that issued the token"},"sub":{"type":"string","format":"uuid","example":"2205be55-cebc-4cde-b88c-f662c16601bc","description":"Subject - the unique identifier for the user"},"iat":{"type":"integer","example":1761837147,"description":"Issued at - timestamp when the token was issued"},"exp":{"type":"integer","example":1761838947,"description":"Expiration - timestamp when the token expires"},"type":{"type":"string","enum":["ID_TOKEN"],"example":"ID_TOKEN","description":"Token type"}},"required":["aud","iss","sub","iat","exp","type"]},"VerificationStartRequest":{"type":"object","properties":{"user_identifier":{"type":"string","description":"User identifier (REQUIRED)","example":"john.doe@example.com"}},"required":["user_identifier"]},"VerificationId":{"type":"object","properties":{"verification_id":{"type":"string","description":"Unique identifier for the biometric verification","example":"inq_abc123xyz"}},"required":["verification_id"]},"VerificationStatus":{"type":"object","properties":{"verification_id":{"type":"string","description":"Unique identifier for the biometric verification","example":"inq_abc123xyz"},"status":{"type":"string","enum":["pending","approved","declined","review"],"description":"Status of the biometric verification","example":"approved"},"updated_at":{"type":"string","format":"date-time","description":"Timestamp when the status was last updated","example":"2024-01-15T10:30:00Z"}},"required":["verification_id","status"]},"VerificationSimulateRequest":{"type":"object","description":"Request payload for simulating biometric verification events for testing in non-production environments.","properties":{"verification_id":{"type":"string","description":"Biometric verification ID to simulate an action for.","example":"inq_abc123xyz"},"action":{"type":"string","description":"Biometric verification simulation action type.","enum":["start_inquiry","complete_inquiry","fail_inquiry","expire_inquiry","mark_for_review_inquiry","approve_inquiry","decline_inquiry"],"example":"approve_inquiry"}},"required":["verification_id","action"]},"VerificationSimulateResponse":{"type":"object","description":"Response payload from the verification simulation endpoint.","properties":{"ok":{"type":"boolean","description":"Indicates whether the simulation request was processed successfully.","example":true},"inquiryId":{"type":"string","description":"The biometric verification ID that was simulated.","example":"inq_abc123xyz"}},"required":["ok","inquiryId"]},"Organisation":{"type":"object","description":"Organisation information. All fields are optional.","properties":{"customer_id":{"type":"string","description":"Customer identifier (OPTIONAL)","example":"cust-12345"},"customer_name":{"type":"string","description":"Customer name (OPTIONAL)","example":"Acme Corporation"}}},"Package":{"type":"object","description":"Package information. All fields are optional.","properties":{"package_id":{"type":"string","description":"Package identifier (OPTIONAL)","example":"pkg-67890"},"package_name":{"type":"string","description":"Package name (OPTIONAL)","example":"Production Wallet"}}},"KeyGenerateRequest":{"type":"object","properties":{"user_identifier":{"type":"string","description":"User identifier (REQUIRED)","example":"john.doe@example.com"},"verification_id":{"type":"string","description":"Verification ID from a previous verification. (REQUIRED) Must exist in identity_verifications table and belong to the user associated with the organisation.","example":"a7b8c9d0-e1f2-4345-a678-901234567890"},"sign_with":{"type":"array","description":"Optional context fields to bind into the enclave signature. public_key is always signed implicitly and must not be included. Every requested field must resolve to a non-empty value, except key_fingerprint which may resolve after generation. Each field may only be listed once.","items":{"type":"string","enum":["external_customer_id","external_package_id","pulled_by_id","pulled_by_type","user_id","key_id","key_fingerprint"]},"example":["external_customer_id","user_id","key_fingerprint"]}},"required":["user_identifier","verification_id"]},"KeyGenerateResponse":{"type":"object","properties":{"key_id":{"type":"string","format":"uuid","description":"Unique identifier for the generated key (UUID)","example":"550e8400-e29b-41d4-a716-446655440000"},"public_key":{"type":"string","description":"Public key in hexadecimal format","example":"30820122300d06092a864886f70d..."},"signature":{"type":"string","description":"Base64 ECDSA P-256 DER signature. When sign_with is omitted, this signs the bare public_key hex; otherwise it signs signed_payload. Verify it with the verification key CoinCover issued you during integration, not with public_key.","example":"MEUCIQDxyz123abc456def789ghi..."},"signed_payload":{"type":"string","description":"RFC 8785 JCS-canonical JSON string signed by the enclave. Present only when sign_with was supplied. Verify its exact UTF-8 bytes without re-stringifying.","example":"{\"external_customer_id\":\"acme-wallet\",\"public_key\":\"30820122300d...\"}"},"signed_fields":{"type":"array","description":"Validated context fields included in signed_payload, in request order. Does not include public_key. Present only when sign_with was supplied.","items":{"type":"string","enum":["external_customer_id","external_package_id","pulled_by_id","pulled_by_type","user_id","key_id","key_fingerprint"]},"example":["external_customer_id","user_id","key_fingerprint"]}},"required":["key_id","public_key","signature"]},"BackupStoreRequest":{"type":"object","properties":{"user_identifier":{"type":"string","description":"User identifier (REQUIRED)","example":"john.doe@example.com"},"verification_id":{"type":"string","description":"Verification ID from a previous verification. (REQUIRED) Must exist in identity_verifications table and belong to the user associated with the organisation.","example":"a7b8c9d0-e1f2-4345-a678-901234567890"},"public_key":{"type":"string","description":"Public key in hexadecimal format. (REQUIRED) 32-4096 characters long.","example":"30820122300d06092a86486f70d...","minLength":32,"maxLength":4096,"pattern":"^[0-9a-fA-F]+$"},"backup":{"type":"array","description":"Array of backup items to store. (REQUIRED) Allows multiple pieces of data to be backed up in a single request. Limited to a maximum of 3 items.","minItems":1,"maxItems":3,"items":{"type":"object","properties":{"item_key":{"type":"string","description":"Unique identifier for the backup item (e.g., \"seed_phrase\", \"private_key\", \"wallet_config\"). (REQUIRED)","example":"seed_phrase"},"item_value":{"type":"string","format":"base64","description":"Base64 encoded encrypted data. (REQUIRED)","example":"SGVsbG8gV29ybGQhIFRoaXMgaXMgbXk..."},"item_checksum":{"type":"string","description":"SHA-256 hash of the data. (REQUIRED) 64 hex characters.","example":"e3b0c44298fc1c149afbf4c8996f...","pattern":"^[a-fA-F0-9]{64}$"}},"required":["item_key","item_value","item_checksum"]}},"metadata":{"type":"object","description":"Optional metadata associated with the backup. Allows custom key-value pairs.","properties":{"description":{"type":"string","description":"Description of the backup (OPTIONAL)","example":"User's private seed phrase"},"content_type":{"type":"string","description":"MIME type of the content (OPTIONAL)","example":"text/plain"},"original_filename":{"type":"string","description":"Original filename if applicable (OPTIONAL)","example":"seed.txt"}},"additionalProperties":true}},"required":["user_identifier","verification_id","public_key","backup"]},"BackupStoreItemResponse":{"type":"object","properties":{"item_key":{"type":"string","description":"Unique identifier for the backup item (e.g., \"seed_phrase\", \"private_key\", \"wallet_config\"). Always present.","example":"seed_phrase"},"backup_id":{"type":"string","format":"uuid","description":"Unique identifier for the stored backup (UUID format). Always present.","example":"550e8400-e29b-41d4-a716-446655440000"},"checksum":{"type":"string","description":"SHA-256 checksum of the stored data (echoed from request). Always present.","example":"c557eec878dfd852ba3f88087c4f350f09c55537ab5e549c3cd14320ec3cef38","pattern":"^[a-fA-F0-9]{64}$"},"data_size":{"type":"integer","description":"Size of the stored data in bytes. Always present.","example":1024,"minimum":0}},"required":["item_key","backup_id","checksum","data_size"]},"BackupStoreResponse":{"type":"object","properties":{"backup_type":{"type":"string","enum":["data"],"description":"Type of backup. Always present. Value is always \"data\".","example":"data"},"metadata":{"type":"object","description":"Metadata associated with the backup (echoed from request + enhanced with system metadata such as API key info, stored_at timestamp, and backup_items_count). Always present.","properties":{"description":{"type":"string","description":"Description of the backup","example":"Test backup with 3 items"},"content_type":{"type":"string","description":"MIME type of the content","example":"application/json"},"original_filename":{"type":"string","description":"Original filename if applicable","example":"backup.json"},"api_key_entity_type":{"type":"string","description":"Entity type from API key authorizer context","example":"PARTNER"},"stored_at":{"type":"string","format":"date-time","description":"ISO timestamp when backups were stored","example":"2025-11-30T20:15:20.123Z"},"backup_items_count":{"type":"integer","description":"Number of backup items successfully stored","example":3,"minimum":0}},"additionalProperties":true},"backup":{"type":"array","description":"Array of stored backup items. Each item contains the item_key, backup_id, checksum, and data_size.","minItems":1,"items":{"$ref":"#/components/schemas/BackupStoreItemResponse"}}},"required":["backup_type","metadata","backup"]},"BackupRecoverRequest":{"type":"object","properties":{"user_identifier":{"type":"string","description":"User identifier (REQUIRED)","example":"john.doe@example.com"},"verification_id":{"type":"string","description":"Verification ID from a successful verification. (REQUIRED) Must be approved/successful.","example":"inq_abc123xyz"},"gpg_public_key":{"type":"string","description":"GPG public key in PGP public key block format for encrypting the recovered data. (REQUIRED)","example":"-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----","pattern":"^-----BEGIN PGP PUBLIC KEY BLOCK-----[\\s\\S]*?-----END PGP PUBLIC KEY BLOCK-----$"}},"required":["user_identifier","verification_id","gpg_public_key"]},"EncryptedDataSchema":{"oneOf":[{"type":"object","description":"GPG encryption/OpenPGP format","properties":{"algorithm":{"type":"string","enum":["gpg"],"description":"Encryption algorithm: GPG encryption/OpenPGP","example":"gpg"},"data":{"type":"string","format":"base64","description":"Base64-encoded encrypted data","example":"eyJhbGciOiJSU0EtT0FFUC1TSEEyNTYifQ..."}},"required":["algorithm","data"],"additionalProperties":false,"example":{"algorithm":"gpg","data":"LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCkNpY2VsbGEgZGljdGEgc2VkIGVsZWlmZW5kLi4uCg=="}},{"type":"object","description":"Direct RSA-OAEP encryption for small data","properties":{"algorithm":{"type":"string","enum":["rsa-direct"],"description":"Encryption algorithm: Direct RSA-OAEP encryption for small data","example":"rsa-direct"},"data":{"type":"string","format":"base64","description":"Base64-encoded encrypted data","example":"eyJhbGciOiJSU0EtT0FFUC1TSEEyNTYifQ..."}},"required":["algorithm","data"],"additionalProperties":false,"example":{"algorithm":"rsa-direct","data":"aGVsbG8gd29ybGQgdGhpcyBpcyBhIHNtYWxsIG1lc3NhZ2UgdGhhdCBjYW4gYmUgZW5jcnlwdGVkIGRpcmVjdGx5IHdpdGggUlNB"}},{"type":"object","description":"Hybrid encryption: AES-256-GCM + RSA-OAEP for large data","properties":{"algorithm":{"type":"string","enum":["hybrid-rsa-aes256-gcm"],"description":"Encryption algorithm: Hybrid encryption (AES-256-GCM + RSA-OAEP) for large data","example":"hybrid-rsa-aes256-gcm"},"data":{"type":"string","format":"base64","description":"Base64-encoded encrypted data","example":"eyJhbGciOiJSU0EtT0FFUC1TSEEyNTYifQ..."},"encryptedKey":{"type":"string","format":"base64","description":"RSA-encrypted AES key (base64)","example":"rsa-encrypted-aes-key..."},"iv":{"type":"string","format":"base64","description":"AES-GCM initialization vector (base64, 12 bytes)","example":"initialization-vector..."},"tag":{"type":"string","format":"base64","description":"AES-GCM authentication tag (base64, 16 bytes)","example":"authentication-tag..."}},"required":["algorithm","data","encryptedKey","iv","tag"],"additionalProperties":false,"example":{"algorithm":"hybrid-rsa-aes256-gcm","data":"dGhpcyBpcyBhIGxhcmdlIG1lc3NhZ2UgdGhhdCByZXF1aXJlcyBoeWJyaWQgZW5jcnlwdGlvbi4uLg==","encryptedKey":"YWJjZGVmZ2hpams=","iv":"MTIzNDU2Nzg5MGFi","tag":"YWJjZGVmZ2hpams="}}],"discriminator":{"propertyName":"algorithm"}},"BackupRecoverItemResponse":{"type":"object","properties":{"backup_id":{"type":"string","format":"uuid","description":"Unique identifier for the backup (UUID format). Always present.","example":"550e8400-e29b-41d4-a716-446655440000"},"backup_item_key":{"type":"string","description":"Unique identifier for the backup item (e.g., \"seed_phrase\", \"private_key\", \"wallet_config\"). Present if available from metadata.","example":"seed_phrase"},"recovery_id":{"type":"string","format":"uuid","description":"ID of the recovery record. Present if recovery was successful.","example":"660e8400-e29b-41d4-a716-446655440001"},"recovery_package":{"$ref":"#/components/schemas/EncryptedDataSchema","description":"Recovery package containing the recovered data in encrypted format. Present if recovery was successful. Supports multiple encryption algorithms: GPG, RSA direct, or hybrid RSA-AES256-GCM."},"original_filename":{"type":"string","description":"Original filename of the backup. Present if recovery was successful and filename was stored.","example":"backup.json"},"content_type":{"type":"string","description":"Content type of the backup. Present if recovery was successful and content type was stored.","example":"application/json"},"original_size":{"type":"string","description":"Size of the original backup in bytes. Present if recovery was successful and size was stored.","example":"1024"},"metadata":{"type":"object","description":"Metadata associated with the backup. Present if metadata was stored.","additionalProperties":true},"error":{"type":"string","description":"Error message if recovery failed. Present only if recovery failed.","example":"Backup not found"}},"required":["backup_id"]},"BackupRecoverResponse":{"type":"object","properties":{"backup":{"type":"array","description":"Array of recovered backup items. Each item contains recovery information or error details.","items":{"$ref":"#/components/schemas/BackupRecoverItemResponse"}}},"required":["backup"]}}},"security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"paths":{"/v1/verification/start":{"post":{"summary":"Start identity verification","description":"Initiates a new identity verification workflow and returns a Verification ID. The user will use this verification_id to spin up a front-end SDK that will guide the user through the identity verification workflow.","operationId":"startVerification","tags":["Verification"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationStartRequest"}}}},"responses":{"200":{"description":"Verification started successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationId"}}}}}}},"/v1/verification/status":{"get":{"summary":"Get verification status","description":"Retrieves the current status of a biometric verification. The status can be in multiple states: 'pending' (initial state), 'approved' (all verifications passed), 'declined' (verifications failed), or 'review' (marked for manual review).","operationId":"getVerificationStatus","tags":["Verification"],"parameters":[{"name":"verification_id","in":"query","required":true,"schema":{"type":"string"},"description":"Verification ID to check status for","example":"inq_abc123xyz"}],"responses":{"200":{"description":"Verification status retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationStatus"}}}}}}},"/v1/verification/simulate":{"post":{"summary":"Simulate biometric verification events","description":"Simulates biometric verification events for testing in non-production environments.","operationId":"simulateVerification","tags":["Verification"],"security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationSimulateRequest"}}}},"responses":{"200":{"description":"Biometric verification event simulated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerificationSimulateResponse"}}}}}}},"/v1/key/generate":{"post":{"summary":"Generate a cryptographic key","description":"Generates a new cryptographic key for a user. Requires a user identifier and a verification_id that exists in the identity_verifications table and belongs to the user associated with the organisation. This endpoint calls the hot-keys orchestrator partner-assign-key endpoint.","operationId":"generateKey","tags":["Keys"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyGenerateRequest"}}}},"responses":{"200":{"description":"Key generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KeyGenerateResponse"}}}}}}},"/v1/backup/store":{"post":{"summary":"Store encrypted backup data","description":"Stores encrypted backup data securely. Accepts multiple backup items in a single request via the backup array (limited to a maximum of 3 items). Requires a verification_id that exists in the identity_verifications table and belongs to the user associated with the organisation. Each backup item's item_key (e.g., \"seed_phrase\", \"private_key\") is stored in the hot-keys database metadata as backup_item_key for recovery operations. This endpoint calls the hot-keys orchestrator partner-secure-data endpoint.","operationId":"storeBackup","tags":["Backups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupStoreRequest"}}}},"responses":{"200":{"description":"Backup stored successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupStoreResponse"}}}}}}},"/v1/backup/recover":{"post":{"summary":"Recover encrypted backup data","description":"Recovers previously stored backup data. Requires successful identity verification (verification_id must be approved/successful). Requires a GPG public key for encrypting the recovered data. Returns an array of backup recovery results, each containing recovery information or error details. This endpoint calls the hot-keys orchestrator recover-backup endpoint for each backup associated with the user.","operationId":"recoverBackup","tags":["Backups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupRecoverRequest"}}}},"responses":{"200":{"description":"Backup recovered successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupRecoverResponse"},"examples":{"gpg_algorithm":{"summary":"GPG encryption example","description":"Example response using GPG encryption algorithm","value":{"backup":[{"backup_id":"550e8400-e29b-41d4-a716-446655440000","backup_item_key":"seed_phrase","recovery_id":"660e8400-e29b-41d4-a716-446655440001","recovery_package":{"algorithm":"gpg","data":"LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCkNpY2VsbGEgZGljdGEgc2VkIGVsZWlmZW5kLi4uCg=="},"original_filename":"backup.json","content_type":"application/json","original_size":"1024"}]}},"rsa_direct_algorithm":{"summary":"RSA direct encryption example","description":"Example response using RSA-OAEP direct encryption for small data","value":{"backup":[{"backup_id":"550e8400-e29b-41d4-a716-446655440000","backup_item_key":"private_key","recovery_id":"660e8400-e29b-41d4-a716-446655440001","recovery_package":{"algorithm":"rsa-direct","data":"aGVsbG8gd29ybGQgdGhpcyBpcyBhIHNtYWxsIG1lc3NhZ2UgdGhhdCBjYW4gYmUgZW5jcnlwdGVkIGRpcmVjdGx5IHdpdGggUlNB"},"original_filename":"key.txt","content_type":"text/plain","original_size":"256"}]}},"hybrid_algorithm":{"summary":"Hybrid RSA-AES256-GCM encryption example","description":"Example response using hybrid encryption (AES-256-GCM + RSA-OAEP) for large data","value":{"backup":[{"backup_id":"550e8400-e29b-41d4-a716-446655440000","backup_item_key":"wallet_config","recovery_id":"660e8400-e29b-41d4-a716-446655440001","recovery_package":{"algorithm":"hybrid-rsa-aes256-gcm","data":"dGhpcyBpcyBhIGxhcmdlIG1lc3NhZ2UgdGhhdCByZXF1aXJlcyBoeWJyaWQgZW5jcnlwdGlvbi4uLg==","encryptedKey":"YWJjZGVmZ2hpams=","iv":"MTIzNDU2Nzg5MGFi","tag":"YWJjZGVmZ2hpams="},"original_filename":"wallet.json","content_type":"application/json","original_size":"2048"}]}}}}}}}}}}}