Wallet Easy Recovery
Move a wallet between two devices through a five-minute mailbox this service cannot open.
Send a fresh X25519 public key; keep its private half on the device. Show a QR with the returned id and that key: nothing else, no secret and no server URL. The session lives five minutes.
Requires the payments:write scope. The shared public key may call it.
apiKeyAuthorizationBearer <API key>Your Cosmos Pay secret API key, sent as Authorization: Bearer <apiKey>.
application/json- body
Opens an easy-recovery session from the new device, with the key the wallet will be sealed to.
receiverKey*stringThe new device’s X25519 public key: 32 bytes, base64url without padding (43 characters). Generate a fresh key pair for every session and keep the private half on the device. The old device seals the wallet to this key, so it reads it from the QR, never from this service.
43 <= length <= 43Created
The body, query or path failed validation. message is an array of the individual failures.
Routes that reject a valid request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
no_authenticated_consumer: The gateway forwarded no API key identity
insufficient_scope: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked x-cosmos-public-key, with a message saying so; more scopes will not help, the key is the wrong one.
gateway_required: the request did not arrive through APISIX.
The budget in x-cosmos-rate-limit is spent for this caller. Honour Retry-After.
The ratelimit-* triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers 503 provider_unavailable.
Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
curl -X POST 'https://api.cosmospay.lat/cosmos-api/v1/wallet/easy-recovery/sessions' \ -H 'Content-Type: application/json' \ -d '{ "receiverKey": "E75P6uryBMf9M1j8nAByGIHRdCeBKCJ-xnTzf3_pe20"}'{ "id": "q2Z7rT0aV1bX9cD4eF6gHw", "expiresAt": "2026-10-28T20:05:00.000Z"}Both devices poll it: the new one waits for offered to ask for the code, the old one for claimed to stop showing it. Answers the status and the expiry only, never the key, the sealed wallet or the code. An unknown, expired or burned session is a 404.
Requires the payments:read scope. The shared public key may call it.
apiKeyAuthorizationBearer <API key>Your Cosmos Pay secret API key, sent as Authorization: Bearer <apiKey>.
id*stringThe session id from POST /v1/wallet/easy-recovery/sessions, as the QR carries it: 22 base64url characters.
OK
The body, query or path failed validation. message is an array of the individual failures.
Routes that reject a valid request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
no_authenticated_consumer: The gateway forwarded no API key identity
insufficient_scope: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked x-cosmos-public-key, with a message saying so; more scopes will not help, the key is the wrong one.
gateway_required: the request did not arrive through APISIX.
easy_recovery_not_found: No such session, or it expired, was claimed or was burned
The budget in x-cosmos-rate-limit is spent for this caller. Honour Retry-After.
The ratelimit-* triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers 503 provider_unavailable.
Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
curl -X GET 'https://api.cosmospay.lat/cosmos-api/v1/wallet/easy-recovery/sessions/q2Z7rT0aV1bX9cD4eF6gHw'{ "status": "offered", "expiresAt": "2026-10-28T20:05:00.000Z"}After the person confirmed and unlocked on the old device: seal the wallet to the receiverKey read from the QR (ECIES, on the device), generate a random five-digit code, send both here and show the code. Accepted once per session, while it is open: the first offer wins and any later one is a 409. Only a hash of the code is stored.
Requires the payments:write scope. The shared public key may call it.
apiKeyAuthorizationBearer <API key>Your Cosmos Pay secret API key, sent as Authorization: Bearer <apiKey>.
id*stringThe session id from POST /v1/wallet/easy-recovery/sessions, as the QR carries it: 22 base64url characters.
application/json- body
The old device's offer: the wallet sealed to the session's key, and the code shown on its screen.
ciphertext*stringThe wallet’s secret sealed on the old device to the session’s receiverKey (ECIES), base64url. This service stores it as given, cannot open it, and hands it out once, to the right code.
1 <= length <= 8192code*stringThe five-digit code the old device generated at random and shows on its own screen. Only its hash is stored.
^[0-9]{5}$OK
The body, query or path failed validation. message is an array of the individual failures.
Routes that reject a valid request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
no_authenticated_consumer: The gateway forwarded no API key identity
insufficient_scope: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked x-cosmos-public-key, with a message saying so; more scopes will not help, the key is the wrong one.
gateway_required: the request did not arrive through APISIX.
easy_recovery_not_found: No such session, or it expired, was claimed or was burned
easy_recovery_already_offered: The session already holds an offer; the first one wins
The budget in x-cosmos-rate-limit is spent for this caller. Honour Retry-After.
The ratelimit-* triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers 503 provider_unavailable.
Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
curl -X POST 'https://api.cosmospay.lat/cosmos-api/v1/wallet/easy-recovery/sessions/q2Z7rT0aV1bX9cD4eF6gHw/offer' \ -H 'Content-Type: application/json' \ -d '{ "ciphertext": "string", "code": "48213"}'{ "status": "offered"}The right code answers the sealed wallet, once: the session reads claimed from then on and no longer holds it. A wrong code is a 422 with details.attemptsLeft; the fifth wrong code deletes the session and answers 410. Before the old device offered, the answer is 409: poll for offered first. A malformed code is a 400 and costs no attempt.
Requires the payments:write scope. The shared public key may call it.
apiKeyAuthorizationBearer <API key>Your Cosmos Pay secret API key, sent as Authorization: Bearer <apiKey>.
id*stringThe session id from POST /v1/wallet/easy-recovery/sessions, as the QR carries it: 22 base64url characters.
application/json- body
The new device's claim of the sealed wallet, with the code read off the old device.
code*stringThe five-digit code the person read off the old device. A malformed one is a 400 and costs no attempt; a wrong one costs one of five.
^[0-9]{5}$OK
The body, query or path failed validation. message is an array of the individual failures.
Routes that reject a valid request for a reason of their own (an amount, a memo, a state) document the code for it on their own 400.
no_authenticated_consumer: The gateway forwarded no API key identity
insufficient_scope: the API key does not hold the scope this route requires. The shared public API key also gets this on every route that is not marked x-cosmos-public-key, with a message saying so; more scopes will not help, the key is the wrong one.
gateway_required: the request did not arrive through APISIX.
easy_recovery_not_found: No such session, or it expired, was claimed or was burned
easy_recovery_not_offered: Nothing has been offered on the session yet
easy_recovery_burned: That was the last wrong code allowed: the session was deleted
easy_recovery_code_invalid: Wrong code; details.attemptsLeft says how many more the session survives
The budget in x-cosmos-rate-limit is spent for this caller. Honour Retry-After.
The ratelimit-* triple rides along on successful responses too, so a client can pace itself instead of being refused. If the counter store itself is unreachable the guard fails closed and answers 503 provider_unavailable.
Unexpected server error. The detail is logged server-side and never returned: there is nothing in the body to branch on beyond the code.
curl -X POST 'https://api.cosmospay.lat/cosmos-api/v1/wallet/easy-recovery/sessions/q2Z7rT0aV1bX9cD4eF6gHw/claim' \ -H 'Content-Type: application/json' \ -d '{ "code": "48213"}'{ "ciphertext": "string"}Account Recovery (SEP-10 / SEP-30)
Served by the two recovery deployments only. Standard SEP-10 web authentication and SEP-30 account recovery: each server holds half of what a recovery signature needs, and proves the person’s inbox on its own.
Webhooks
Register endpoints, manage them, and inspect delivery attempts for payment events.