Backup API
All endpoints use the auth host. No endpoint below accepts a plaintext backup password, seed phrase, or private key as a backup field.
Authentication by route
| Route | Authentication |
|---|---|
GET /api/backup without a selector | Better Auth session; resolves its primary/available profile |
GET /api/backup?bapId=… | Owned Better Auth session or valid Bitcoin ownership signature |
GET /api/backup?oauthId=… | Session owning the linked provider account and mapped BAP identity |
POST /api/backup | Supported OAuth access token or Better Auth session |
GET /api/backup/status | Supported OAuth access token or Better Auth session |
POST /api/backup/link-oauth | OAuth bearer access token and an already-linked provider account |
GET, POST /api/backup/sync | Authorization: Bitcoin-Auth … with BAP ownership verification |
The recovery selector uses oauthId=provider|accountId.
For OAuth access, the token must resolve the selected BAP identity. Do not treat oauthId as a public retrieval secret or assume a session cookie works on the bearer-only linking route.
Upload and retrieve
POST /api/backup accepts:
{ "encryptedBackup": "encrypted string produced locally by bitcoin-backup" }It returns success, bapId, and message. The regular upload increments backup generation. The authenticated identity determines the destination; do not send a password or assume an arbitrary body bapId changes ownership.
GET /api/backup returns:
{
"bapId": "the-authorized-identity",
"backup": "encrypted string",
"lastUpdated": "ISO timestamp",
"generation": 1
}The returned backup is ciphertext. Decrypt it locally and validate the format/identity before replacing working local data.
Status and recovery linking
GET /api/backup/status returns hasBackup, updatedAt, userId, bapId, and linkedOAuthAccounts entries containing provider/account identifiers and link timestamps.
POST /api/backup/link-oauth accepts { provider, providerAccountId }. The provider account must already belong to the authenticated user. An absent link returns 403 (ACCOUNT_NOT_LINKED); a conflicting recovery association can return 409 (OAUTH_ALREADY_LINKED). This route records a recovery mapping; it does not log in to the provider or encrypt/upload a backup.
Machine synchronization
GET /api/backup/sync?bapId=… retrieves an owned backup. POST /api/backup/sync accepts { bapId, encryptedBackup }. Sign the path /api/backup/sync with the owning key and use the endpoint's Bitcoin-Auth header contract. A matching Sigma profile/account must already exist; otherwise upload returns 404. The sync upsert does not increment generation, so it is not interchangeable with ordinary account backup upload for generation-based reconciliation.
This route's current verifier checks path/freshness and BAP ownership; do not infer a body-binding guarantee from the presence of a Bitcoin header. Use TLS and the supported sync client contract. It has a separate process-local 20/minute IP limiter.
Persistence and limits
Backups and recovery mappings are stored durably in PostgreSQL, with caching used where applicable. No public backup DELETE endpoint or ten-year retention promise is documented here. Keep offline encrypted copies and verify updates before discarding old recovery material.