{"revision":"0ed62d2c742f8e20","language":"en","entry":{"id":"publish-alert","kind":"endpoint","group":"manage","title":"Declare an account stolen, abandoned or moved","method":"POST","path":"/v1/identities/{n}/alerts","paths":[],"summary":"The owner signs an alert about one of the card’s linked accounts. From then on every lookup of that account opens with it, until a regained alert ends it.","response":"201 with the alert as GET /v1/identities/{n}/alerts gives it, its log entry and proof included.","parameters":[{"name":"n","description":"Permanent positive card number.","in":"path","required":true}],"sample":"/v1/identities/1/alerts","access":"signed","base":"directory","notes":["Signed by an active key of the person: a device key or the recovery key of the backup file, the key that is left when a device was stolen with the account.","At most 10 alerts per card in 24 hours. Organisations have none."],"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 identity_banned","403 key_banned","403 key_mismatch","404 not_found","409 action_pending","409 alert_in_force","409 alert_not_in_force","409 erase_pending","409 replayed_nonce","409 signer_stale","410 erased","413 payload_too_large","422 account_not_linked","422 bad_entry","422 invalid_statement","422 not_a_person","429 alert_quota","429 rate_limited","500 internal_error","503 busy","503 log_unavailable"],"related":["card-alerts","lookup","signed-requests"],"sections":[{"kind":"code","language":"shell","code":"curl -fsS -X POST \\\n  -H \"X-Public-Key: $PUBLIC_KEY\" \\\n  -H \"X-Signature: $REQUEST_SIGNATURE\" --json @request.json \\\n  \"https://veil.rip/v1/identities/1/alerts\""}],"live":false,"bodyFields":[{"name":"statement","description":"{platform, since, state, subject, time, to, type: \"alert\", v: 1}. state is stolen (since a date, YYYY-MM-DD), abandoned, moved (to = {handle, subject}) or regained.","in":"body","required":true},{"name":"salt","description":"32 random bytes in 64 lowercase hex characters, chosen by the browser.","in":"body","required":true},{"name":"entry","description":"{text, signature}: the log entry {c, key, kind, signer, time, token, v: 2} in canonical JSON without its signature, and the signature of \"veil-rubrica:card-entry:v2\\n\" + text with X-Public-Key.","in":"body","required":true}],"responses":[{"status":201,"contentType":"application/json","description":"The alert, its entry and its proof.","fields":[{"name":"id","type":"integer","description":"The entry’s position in the log: the statement’s permanent id."},{"name":"kind","type":"string","description":"statement or alert."},{"name":"n","type":"integer","description":"The card the statement is by."},{"name":"signer","type":"object","description":"n and token of the person card whose key signed: the card itself, or an owner or admin of an organisation."},{"name":"statement","type":"string","description":"The statement as signed, canonical JSON. Its fields are repeated below for display."},{"name":"salt","type":"string","description":"32 bytes in hex: c of the entry is SHA-256(salt ‖ statement)."},{"name":"entry","type":"string","description":"The log entry with its signature, as the log holds it."},{"name":"i","type":"integer","description":"The entry’s position in the log (the same as id)."},{"name":"publishedAt","type":"string | null","description":"The earliest cosignature on the first checkpoint holding the entry."},{"name":"checkpoint","type":"object | null","description":"size and note of the cosigned checkpoint that dates the entry. Null when the log could not be read."},{"name":"inclusion","type":"array<string> | null","description":"The entry’s RFC 6962 inclusion proof in that checkpoint, hex, deepest first."},{"name":"platform","type":"string","description":"The platform of the account."},{"name":"subject","type":"string","description":"The account’s stable id, as GET /v1/lookup takes it."},{"name":"display","type":"string","description":"The handle the card shows for it."},{"name":"state","type":"string","description":"stolen, abandoned, moved or regained."},{"name":"since","type":"string | null","description":"For stolen: the date it was stolen, YYYY-MM-DD."},{"name":"to","type":"object | null","description":"For moved: handle, and subject when the new account is linked to the card."},{"name":"logIndex","type":"integer","description":"The entry’s position in the log."}]}],"responseObjects":[],"errorFormat":"json","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":"Ask again each time: a new statement or alert shows at once."}],"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":"common"},{"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":"common"},{"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":"common"},{"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":"common"},{"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":"common"},{"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":"common"},{"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":"common"},{"status":403,"code":"identity_banned","description":"The acting identity is suspended by Veil staff.","action":"Wait for the restriction to end or ask staff to review it.","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":403,"code":"key_mismatch","description":"The signing key does not have authority over this card or operation.","action":"Use an active device key with the required owner or member role.","scope":"endpoint"},{"status":404,"code":"not_found","description":"The requested card, key, alias, code or resource was not found.","action":"Check the identifier. Temporary codes and previews may already have expired or been used.","scope":"endpoint"},{"status":409,"code":"action_pending","description":"An earlier alert about the same account, or a staff action of the same family, is still waiting for the log.","action":"Try again once it is published, within a few minutes.","scope":"endpoint"},{"status":409,"code":"alert_in_force","description":"The account already carries this same alert.","action":"Nothing to do. To correct the date or the new account, send the alert with the new details.","scope":"endpoint"},{"status":409,"code":"alert_not_in_force","description":"`regained` ends an alert, and the account has none in force.","action":"Read the card’s alerts (GET /v1/identities/{n}/alerts) before ending one.","scope":"endpoint"},{"status":409,"code":"erase_pending","description":"On a format 2 log, the card has an erasure waiting. Its signed closing names the current version, so the card cannot publish until the erasure is cancelled.","action":"Cancel the erasure with POST /v1/identities/{n}/erase/cancel, then publish the version.","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":"common"},{"status":409,"code":"signer_stale","description":"On a format 2 log, `signer.n` of a signed entry is behind the current version of the card that signs.","action":"Read the signing card again and sign the entry with its current version in `signer.n`.","scope":"endpoint"},{"status":410,"code":"erased","description":"The card existed and was erased or removed. The response also carries its public erasure receipt.","action":"Stop using this card as current authority. Retain only the public receipt if needed.","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":"common"},{"status":422,"code":"account_not_linked","description":"The alert is about an account the card does not hold, or `to.subject` is not another account of the card on the same platform.","action":"Name a linked account, or leave `to.subject` null and write the new handle.","scope":"endpoint"},{"status":422,"code":"bad_entry","description":"On a format 2 log, the `salt`, `entry` or `closing` of a request is wrong. `error.message` names the field.","action":"Correct the named field, sign the entry again with the request key and send a new signed request.","scope":"endpoint"},{"status":422,"code":"invalid_statement","description":"A card’s alert or statement does not follow its schema: a missing or extra field, a text too long or with hidden characters, a link that is not https, a date in the future.","action":"Fix the field named in `error.message` and sign the statement and its entry again.","scope":"endpoint"},{"status":422,"code":"not_a_person","description":"The card is an organisation where a person is required.","action":"Use the organisation endpoint or an eligible person’s card.","scope":"endpoint"},{"status":429,"code":"alert_quota","description":"The card made 10 account alerts in the last 24 hours.","action":"Wait the seconds that `Retry-After` gives.","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":"common"},{"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":"common"},{"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":"common"},{"status":503,"code":"log_unavailable","description":"The public log cannot currently be read, verified or used for publication.","action":"For reads, keep the result unverified and try later. Before retrying a write, read the card to check whether publication completed.","scope":"endpoint"}],"documentationUrl":"https://veil.rip/docs/publish-alert","supportsBrowserFetch":false,"browserMode":"local-or-server"}}