{"revision":"0ed62d2c742f8e20","language":"en","entry":{"id":"external-keys-file","kind":"endpoint","group":"keys","title":"Get a card’s public keys from elsewhere as a file","method":"GET","path":"/u/{card}/keys.txt","paths":["/u/{card}.keys"],"summary":"Feed a person’s SSH, minisign and OpenPGP public keys to git, ssh-keygen, a server, minisign or gpg: all of them, or one key by its fingerprint.","response":"A text file. Comment lines say the card, when the server read it and where each proof is checked; then one line per key in the chosen format. With format=pgp the comments are the Comment lines of one armoured block that holds every OpenPGP key of the card.","parameters":[{"name":"card","description":"A person’s permanent card number or its current alias.","in":"path","required":true},{"name":"format","description":"allowed_signers for git and ssh-keygen -Y verify, authorized_keys (the default) for a server’s ~/.ssh/authorized_keys, minisign for minisign -V -p, pgp for gpg --import.","in":"query","required":false},{"name":"fp","description":"Optional. Keep one key: its fingerprint as ssh-keygen -l, minisign or gpg print it (SHA256:…, the 16-digit minisign key ID, the 40 OpenPGP hex digits), or the public key itself. Write the + of an SSH fingerprint as %2B.","in":"query","required":false},{"name":"key","description":"Optional. Another name for fp; give one of the two.","in":"query","required":false}],"sample":"/u/1/keys.txt?format=allowed_signers","access":"public","base":"directory","notes":["The file is the server’s word. Each key’s proof of possession is checked in the reader’s browser on the card, or by your client from state.external of the card JSON.","allowed_signers names each key with the card’s verified email addresses and veil.rip/u/<n>, with no namespace restriction, so commit signatures (namespace git) verify too.","format=pgp gives an armoured public key block: Comment lines that name the card, the time the server read it and where the proofs are checked, then the card’s OpenPGP keys one after another and the CRC24 line. A card without OpenPGP keys gives the comment lines alone, each starting with #. gpg skips a key without a user ID.","GET /u/{card}.keys is the same file at the address github.com/<user>.keys made familiar: authorized_keys without parameters, the same format, fp and key, the same headers and errors. An alias never holds a dot, so it is never a card page.","With fp the file holds that one key, after the same comment lines; the first one names the key. A key the card no longer holds is a 404."],"errors":["400 invalid_parameter","401 invalid_api_key","403 api_key_scope","403 api_key_suspended","403 developer_suspended","404 not_found","410 erased","429 rate_limited","500 internal_error","503 busy"],"related":["external-keys","person-card","lookup","publish-person"],"sections":[{"kind":"code","language":"shell","code":"curl -fsS 'https://veil.rip/u/1/keys.txt?format=allowed_signers' > allowed_signers\ngit config gpg.ssh.allowedSignersFile \"$PWD/allowed_signers\""},{"kind":"code","language":"shell","code":"curl -fsS https://veil.rip/u/1.keys >> ~/.ssh/authorized_keys"},{"kind":"code","language":"shell","code":"curl -fsS 'https://veil.rip/u/1/keys.txt?format=pgp' | gpg --import"},{"kind":"code","language":"shell","code":"curl -fsS 'https://veil.rip/u/1/keys.txt?format=allowed_signers&fp=SHA256:yKGK3rJbLA9EAbnKwNXuQvLvsvT0WRrii16h6sF9Rwk' > allowed_signers"}],"verify":"Check each key’s proof on the card before you trust the file for anything that matters: the challenge names the card, the device key and a cosigned checkpoint, and the signature must verify with the key.","live":false,"responses":[{"status":200,"contentType":"text/plain","description":"A text file. Comment lines say the card, when the server read it and where each proof is checked; then one line per key in the chosen format. With format=pgp the comments are the Comment lines of one armoured block that holds every OpenPGP key of the card.","fields":[]}],"responseObjects":[],"errorFormat":"json","errorDetails":[{"status":400,"code":"invalid_parameter","description":"The format is not one of allowed_signers, authorized_keys, minisign or pgp; fp is neither a fingerprint nor a public key; fp and key differ; or the format cannot carry the type of the key fp names (the message names the format that can).","action":"Leave format out for authorized_keys or name one of the four, and ask for a key in the format the message names.","scope":"endpoint"},{"status":401,"code":"invalid_api_key","description":"The bearer credential is malformed, unknown, expired or revoked.","action":"Use an active key. To use anonymous public access, remove `Authorization` entirely. Invalid credentials never fall back automatically.","scope":"optional-api-key"},{"status":403,"code":"api_key_scope","description":"The API key lacks read or verify scope, or the endpoint does not accept bearer keys.","action":"Use the correct scope. Identity writes and key management still require a device signature.","scope":"optional-api-key"},{"status":403,"code":"api_key_suspended","description":"This API key is suspended.","action":"Ask staff to restore it; another key cannot lift an account suspension.","scope":"optional-api-key"},{"status":403,"code":"developer_suspended","description":"The owner’s API account is suspended or the owner card is unavailable.","action":"Check the owner account and any staff restriction. Creating another key does not bypass suspension.","scope":"optional-api-key"},{"status":404,"code":"not_found","description":"No person card has this number or alias, or the card holds no key with the fingerprint fp names.","action":"Check the card, and read its current keys from the card or from the file without fp.","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":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"}],"responseHeaders":[{"name":"Content-Type","value":"text/plain; charset=utf-8","description":"Use the declared media type before choosing a parser."},{"name":"Cache-Control","value":"no-cache","description":"Ask again each time: a removed key leaves the file at once."}],"documentationUrl":"https://veil.rip/docs/external-keys-file","supportsBrowserFetch":true,"browserMode":"fetch"}}