Backing up your own wallets rather than your customers’?
You want Recovery for Institutions, which uses the standard endpoints. This guide is for providers holding recovery material on behalf of many downstream customers.
You want Recovery for Institutions, which uses the standard endpoints. This guide is for providers holding recovery material on behalf of many downstream customers.
The shape of an integration
Every Partner Key Service integration follows the same three moves:1
Assign a key
You call us with an identifier for your customer. We generate an RSA key pair in our enclaves and return the public key, hex-encoded.
2
Encrypt client-side
Your customer’s recovery material is encrypted with the public key — RSA-OAEP with SHA-256 — before it ever leaves their environment. We never see plaintext.
3
Store the ciphertext
Send the ciphertext to the secure data or secure file endpoint. We hold it. When recovery is needed, the corresponding private key is retrieved from the enclave to decrypt it.
Step 1 — Authenticate
Authenticate with a Bearer token in theAuthorization header. CoinCover supports both JWT tokens and long-lived API keys. Your sandbox credentials are in your welcome pack. Partner endpoints require a token with partner-endpoint scope.
Step 2 — Choose HOT or COLD at assignment time
Every key sits in one of two environments. The choice is fixed for the life of the key, so pick deliberately.
On the partner endpoint,
key_environment is required — there’s no default.
Step 3 — Assign a key to a customer
CallPOST /v1/partner/keys with an identifier for your customer and your tenant structure. organisation and package are required so the key is filed against the right downstream customer. We return a stable key_id and the public key you’ll use to encrypt their data.
key_id against your customer record. You’ll reference it in your own audit logs and customer-support tooling.
The response also includes a signature over the public_key, signed by the enclave that generated the key. During integration, CoinCover issues you a verification key — use it to verify the signature on every assign-key response. A successful verification confirms the public key really came from a legitimate CoinCover enclave; if it fails, treat the response as untrusted and escalate before encrypting any recovery material.
Optionally, bind business context into the signature
By default the signature covers only thepublic_key. If you want it to also attest to which customer, package, or user a key was issued for, pass a sign_with array on the assign-key request. The enclave signs a canonical JSON payload built from the fields you name plus the public_key (which is always included), so a verified signature proves the key is bound to that exact context — which closes off key-substitution across your customers or packages.
Supported fields: external_customer_id, external_package_id, pulled_by_id, pulled_by_type, user_id, key_id, key_fingerprint. Don’t list public_key — it’s always included implicitly, and passing it is rejected. Every field you do list must resolve to a value, or the request returns a 400.
What each field resolves to:
external_customer_id/external_package_id— yourorganisation.customer_id/package.package_idfrom the request.user_id— CoinCover’s internal user id for theuser_identifieryou sent (not the identifier string).key_id— the id of the key being assigned (the samekey_idreturned in the response).key_fingerprint— the SHA-256 (hex) of the enclave-issued public-key bytes.pulled_by_id/pulled_by_type— the credential CoinCover authenticated the call with (api_keyoruser_credentials). CoinCover sets these from your auth token; they aren’t read from the request body.
sign_with is present, the response carries two extra fields alongside signature:
signed_payload is the exact JCS-canonical JSON string the enclave signed: object keys are sorted and the public_key is always included alongside the fields you requested. signed_fields echoes the fields you requested, in request order (it doesn’t include public_key).
To verify the binding, check the signature over the exact signed_payload bytes with your verification key, then confirm the bound values are the ones you expect:
Node
sign_with is optional and additive. Omit it and the response is unchanged — the signature covers the public_key alone, verified over the UTF-8 bytes of the hex public_key string (not its decoded bytes) — so existing integrations need no changes.Step 4 — Encrypt client-side
The public key comes back hex-encoded. Convert it to PEM, then encrypt with RSA-OAEP using SHA-256 as both the hash and MGF1 hash.The checksum must be SHA-256 of the plaintext, not the ciphertext.
Step 5 — Store encrypted data
Send the ciphertext, the plaintext checksum, and the public key toPOST /v1/partner/secure/data. We use the public key to identify the storage location — there’s no need to repeat the environment. On the partner endpoint the checksum is required.
Step 6 — Upload encrypted files
For binary backups — wallet seed files, encrypted archives — usePOST /v1/partner/secure/file. This is multipart/form-data and expects the file to already be encrypted on your side. CoinCover applies no additional encryption server-side.
Step 7 — Test before you ship
Run through the going to production checklist before you cut any production traffic to live keys. The most common issues we see at this stage are:- Hex-to-PEM conversion off by one byte (always verify with a known-good public key first)
- OAEP hash and MGF1 hash mismatched (both should be SHA-256)
- Checksum computed over ciphertext rather than plaintext
- Sandbox base URL still set in production config
What’s next
API reference
Every endpoint, request, and response.
Testing & sandbox
The sandbox matrix to run before production.