Skip to main content
This guide is for wallet providers who integrate via CoinCover’s Partner Key Service — the API surface for assigning keys to your customers and storing encrypted recovery material on their behalf. It covers the full happy path: assigning a key, encrypting client-side, storing data and files, and getting ready to ship. If your integration runs through a key ceremony rather than the API (Cobo, Copper Unlimited, BitGo), the partner-specific page is the one to read. The Partner Key Service powers the API-based partners — Fireblocks, Utila, Fordefi.
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.

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.
The plaintext key share, seed, or backup file never crosses the boundary. That’s the whole point.

Step 1 — Authenticate

Authenticate with a Bearer token in the Authorization 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.
The full authentication model — scopes, rotation, revocation — is on the authentication page.

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

Call POST /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.
Store the 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 the public_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 — your organisation.customer_id / package.package_id from the request.
  • user_id — CoinCover’s internal user id for the user_identifier you sent (not the identifier string).
  • key_id — the id of the key being assigned (the same key_id returned 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_key or user_credentials). CoinCover sets these from your auth token; they aren’t read from the request body.
When 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.
The full request and response schema is in the API reference.

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.
RSA-OAEP has a maximum message size (around 446 bytes for a 4096-bit key with SHA-256). For anything larger, encrypt the payload with a fresh symmetric key and wrap that symmetric key with the public key — or use the file endpoint, which expects an already-encrypted binary blob.

Step 5 — Store encrypted data

Send the ciphertext, the plaintext checksum, and the public key to POST /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 — use POST /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.
The maximum payload size is 10MB. If you need more, talk to your account manager about chunked upload patterns.

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
The testing page has the full sandbox matrix we recommend.

What’s next

API reference

Every endpoint, request, and response.

Testing & sandbox

The sandbox matrix to run before production.