{"revision":"6ba549855354e504","language":"en","entry":{"id":"start-move","kind":"endpoint","group":"manage","title":"Start moving a browser","method":"POST","path":"/v1/moves","paths":[],"summary":"Ask for a move for this browser's new key, signed by that key. The move is open ten minutes.","response":"201 with nonce, key, expiresAt and approve, the address of the old page that approves it (public data only).","parameters":[],"sample":"/v1/moves","access":"signed","base":"directory","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.","At most 10 an hour per IP, like pairing codes. A key already on a card has nothing to move."],"errors":["400 invalid_json","400 invalid_nonce","400 invalid_parameter","400 invalid_signature_alg","400 invalid_timestamp","400 wrong_format","401 invalid_signature","401 missing_signature","401 stale_timestamp","403 key_banned","404 no_move","409 key_already_used","409 replayed_nonce","413 payload_too_large","429 rate_limited","500 internal_error","503 busy"],"related":["signed-requests"],"bodyFields":[],"live":false,"responseObjects":[],"errorFormat":"json","errorDetails":[{"status":400,"code":"invalid_json","description":"The body is not a JSON object, contains duplicate keys or has trailing data.","action":"Send one valid JSON object with `Content-Type` application/json.","scope":"endpoint"},{"status":400,"code":"invalid_nonce","description":"The signed-request nonce has an invalid format.","action":"Generate a fresh 16 to 128-character nonce using letters, digits, _ or -.","scope":"endpoint"},{"status":400,"code":"invalid_parameter","description":"A path, query or body field is missing, malformed or outside its accepted range.","action":"Correct the field named in `error.message` before sending again.","scope":"endpoint"},{"status":400,"code":"invalid_signature_alg","description":"`X-Signature-Alg` is unsupported.","action":"Use a supported signing algorithm and matching key/signature encoding.","scope":"endpoint"},{"status":400,"code":"invalid_timestamp","description":"The signed-request timestamp is not Unix milliseconds.","action":"Use an integer Unix timestamp in milliseconds and sign the corrected request.","scope":"endpoint"},{"status":400,"code":"wrong_format","description":"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.","action":"Read `format` in GET /v1/log (absent means 1) and send the shape of that format.","scope":"endpoint"},{"status":401,"code":"invalid_signature","description":"The HTTP request signature does not verify.","action":"Sign the exact method, path and body bytes, without changing them afterwards.","scope":"endpoint"},{"status":401,"code":"missing_signature","description":"The request requires a device signature and its headers are missing or unreadable.","action":"Send `X-Public-Key`, `X-Signature` and the signed-request metadata. An API key does not replace a device signature.","scope":"endpoint"},{"status":401,"code":"stale_timestamp","description":"The signed timestamp is outside the accepted clock window.","action":"Compare the server Date header with your clock, then generate a fresh nonce, timestamp and signature.","scope":"endpoint"},{"status":403,"code":"key_banned","description":"The device signing key is suspended by Veil staff.","action":"Use an authorised active key or ask staff to review the restriction.","scope":"endpoint"},{"status":404,"code":"no_move","description":"This directory has one address, so there is no browser to move.","action":"Make a card or log in at this address.","scope":"endpoint"},{"status":409,"code":"key_already_used","description":"A device key already belongs to an identity and cannot be reused by another.","action":"Use the existing card or generate a fresh device key.","scope":"endpoint"},{"status":409,"code":"replayed_nonce","description":"The signing key already used this nonce.","action":"Check whether the previous request succeeded before making a fresh signed request.","scope":"endpoint"},{"status":413,"code":"payload_too_large","description":"The request body exceeds this endpoint’s byte limit.","action":"Reduce the encoded body. API-key management accepts at most 8 KiB.","scope":"endpoint"},{"status":429,"code":"rate_limited","description":"An anonymous, signed-request or API-key allowance was exceeded.","action":"Wait at least `Retry-After` seconds. Share a queue and retry budget across your workers.","scope":"endpoint"},{"status":500,"code":"internal_error","description":"An unexpected server error occurred. Internal details are not returned.","action":"Retry reads with bounded backoff. Check write results before sending a new signed operation.","scope":"endpoint"},{"status":503,"code":"busy","description":"The application has reached a temporary concurrency or database limit.","action":"Wait `Retry-After` seconds and retry with bounded backoff.","scope":"endpoint"}],"sections":[{"kind":"code","language":"shell","code":"curl -fsS -X POST 'https://veil.pub/v1/moves' \\\n  -H \"X-Public-Key: $NEW_KEY\" -H \"X-Signature: $REQUEST_SIGNATURE\" --json @request.json"}],"responses":[{"status":201,"contentType":"application/json","description":"201 with nonce, key, expiresAt and approve, the address of the old page that approves it (public data only).","fields":[{"name":"nonce","type":"string","description":"The move's name in both pages' addresses."},{"name":"key","type":"string","description":"The new key, Ed25519 in hex."},{"name":"expiresAt","type":"string","description":"When the move closes, ISO 8601."},{"name":"approve","type":"string","description":"The old address's page that approves it."}]}],"responseHeaders":[{"name":"Content-Type","value":"application/json","description":"Use the declared media type before choosing a parser."},{"name":"Date","value":"HTTP date","description":"Server time. Compare it with your clock when signing requests."},{"name":"Cache-Control","value":"no-store","description":"Do not persist private or temporary responses in a shared cache."},{"name":"Retry-After","value":"integer seconds, on rate_limited or busy","description":"Wait at least this long before retrying. Other 429/503 codes do not necessarily include it."}],"documentationUrl":"https://veil.pub/docs/start-move","supportsBrowserFetch":false,"browserMode":"local-or-server"}}