Approve a move
At the old address, signed by an active device key of the card: the card's next version that adds the move's key as a device, the receipt and the SHA-256 of the secret. Checked as a publication would be, kept and not published.
/v1/moves/{nonce}/approve- Access
- Signed request
- Client
- Local or server client
Parameters#
| Name | Location | Meaning |
|---|---|---|
nonceRequired | Path | The move's nonce: 22 characters of base64url. |
JSON body#
| Name | Location | Meaning |
|---|---|---|
labelRequired | JSON | The version's label, as for a publication. |
bodyRequired | JSON | The version's body. |
signatureRequired | JSON | The label's signature by the key that signs the request. |
secretHashRequired | JSON | SHA-256 of the move's 32-byte secret, lowercase hex. |
retireRequired | JSON | Whether the old key retires itself afterwards. |
chatRequired | JSON | Whether the old browser holds chat keys to hand over. |
receiptRequired | JSON | The receipt, veil-rubrica:move-receipt:v1 and its eight lines. |
receiptSignatureRequired | JSON | The receipt's Ed25519 signature in hex by the same key. |
This call also needs the common signed-request headers, nonce and timestamp. Prepare and sign request.json before using the example. Request signing
Request examples#
curl -fsS -X POST 'https://veil.pub/v1/moves/MOVE_NONCE/approve' \
-H "X-Public-Key: $OLD_KEY" -H "X-Signature: $REQUEST_SIGNATURE" --json @approval.jsonResponse#
200 with nonce, identity and version.
| Field | Type | Meaning |
|---|---|---|
nonce | string | The move. |
identity | integer | The card's number. |
version | integer | The version that adds the new key. |
Response headers#
| Header | Value | Meaning |
|---|---|---|
Content-Type | application/json | Use the declared media type before choosing a parser. |
Date | HTTP date | Server time. Compare it with your clock when signing requests. |
Cache-Control | no-store | Do not persist private or temporary responses in a shared cache. |
Retry-After | integer seconds, on rate_limited or busy | Wait at least this long before retrying. Other 429/503 codes do not necessarily include it. |
Usage notes#
- Moving a browser from the directory's old address to its new one (since 6 November 2026): a key lives on the origin that made it, so the new address makes a key of its own and the card adds it with a version signed at the old address, in the same browser. The version is kept until the new key comes back with the secret.
Errors#
API errors return an error object. Use error.code for program logic; error.message is English text for people and can change. Response and error guide
| HTTP | Code | Meaning and next step |
|---|---|---|
| 400 | invalid_json | The body is not a JSON object, contains duplicate keys or has trailing data. Send one valid JSON object with |
| 400 | invalid_nonce | The signed-request nonce has an invalid format. Generate a fresh 16 to 128-character nonce using letters, digits, _ or -. |
| 400 | invalid_parameter | A path, query or body field is missing, malformed or outside its accepted range. Correct the field named in |
| 400 | invalid_signature_alg |
Use a supported signing algorithm and matching key/signature encoding. |
| 400 | invalid_timestamp | The signed-request timestamp is not Unix milliseconds. Use an integer Unix timestamp in milliseconds and sign the corrected request. |
| 400 | wrong_format | The request or the read belongs to the other log format than this server’s: a v1 record on a format 2 log, or a format 2 read on a format 1 log. Read |
| 401 | invalid_signature | The HTTP request signature does not verify. Sign the exact method, path and body bytes, without changing them afterwards. |
| 401 | missing_signature | The request requires a device signature and its headers are missing or unreadable. Send |
| 401 | stale_timestamp | The signed timestamp is outside the accepted clock window. Compare the server Date header with your clock, then generate a fresh nonce, timestamp and signature. |
| 403 | key_banned | The device signing key is suspended by Veil staff. Use an authorised active key or ask staff to review the restriction. |
| 403 | signer_not_authorised | The organisation member or signing role cannot publish this change. Check accepted membership and the role required for the change. |
| 404 | no_move | This directory has one address, so there is no browser to move. Make a card or log in at this address. |
| 404 | not_found | The requested card, key, alias, code or resource was not found. Check the identifier. Temporary codes and previews may already have expired or been used. |
| 409 | move_approved | The move was approved already. Complete it from the new address, or start a new move. |
| 409 | move_done | The move is done: the new key is on the card. Nothing to do. Read the card to see the new key. |
| 409 | replayed_nonce | The signing key already used this nonce. Check whether the previous request succeeded before making a fresh signed request. |
| 409 | version_conflict | The version cannot be published against the card’s current state. Read the latest card, reconcile the intended change and sign a new version. |
| 410 | move_expired | The move waited more than ten minutes. Start a new move. |
| 413 | payload_too_large | The request body exceeds this endpoint’s byte limit. Reduce the encoded body. API-key management accepts at most 8 KiB. |
| 422 | invalid_move | The approval is not a move: its version must add exactly the move's key as a device of the card, and its receipt must name the move, the card, the two keys, the version and the choices, signed by the key that signs the request. Build the version and the receipt again from the card and the move as they are now. |
| 429 | rate_limited | An anonymous, signed-request or API-key allowance was exceeded. Wait at least |
| 500 | internal_error | An unexpected server error occurred. Internal details are not returned. Retry reads with bounded backoff. Check write results before sending a new signed operation. |
| 503 | busy | The application has reached a temporary concurrency or database limit. Wait |