veil

Get a card’s public keys from elsewhere as a file

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.

GET/u/{card}/keys.txt
Access
Public; API key optional
Client
Browser fetch supported

Other addresses#

  • /u/{card}.keys

Parameters#

NameLocationMeaning
cardRequiredPathA person’s permanent card number or its current alias.
formatQueryallowed_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.
fpQueryOptional. 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.
keyQueryOptional. Another name for fp; give one of the two.

Request examples#

cURL
curl -fsS 'https://veil.rip/u/1/keys.txt?format=allowed_signers' > allowed_signers
git config gpg.ssh.allowedSignersFile "$PWD/allowed_signers"
cURL
curl -fsS https://veil.rip/u/1.keys >> ~/.ssh/authorized_keys
cURL
curl -fsS 'https://veil.rip/u/1/keys.txt?format=pgp' | gpg --import
cURL
curl -fsS 'https://veil.rip/u/1/keys.txt?format=allowed_signers&fp=SHA256:yKGK3rJbLA9EAbnKwNXuQvLvsvT0WRrii16h6sF9Rwk' > allowed_signers

Response#

HTTP 200text/plain

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.

Response headers#

HeaderValueMeaning
Content-Typetext/plain; charset=utf-8Use the declared media type before choosing a parser.
Cache-Controlno-cacheAsk again each time: a removed key leaves the file at once.

What to 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.

Usage 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#

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

HTTPCodeMeaning and next step
400invalid_parameter

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).

Leave format out for authorized_keys or name one of the four, and ask for a key in the format the message names.

404not_found

No person card has this number or alias, or the card holds no key with the fingerprint fp names.

Check the card, and read its current keys from the card or from the file without fp.

410erased

The card existed and was erased or removed. The response also carries its public erasure receipt.

Stop using this card as current authority. Retain only the public receipt if needed.

When using an optional API key
HTTPCodeMeaning and next step
401invalid_api_key

The bearer credential is malformed, unknown, expired or revoked.

Use an active key. To use anonymous public access, remove Authorization entirely. Invalid credentials never fall back automatically.

403api_key_scope

The API key lacks read or verify scope, or the endpoint does not accept bearer keys.

Use the correct scope. Identity writes and key management still require a device signature.

403api_key_suspended

This API key is suspended.

Ask staff to restore it; another key cannot lift an account suspension.

403developer_suspended

The owner’s API account is suspended or the owner card is unavailable.

Check the owner account and any staff restriction. Creating another key does not bypass suspension.

Shared request errors
HTTPCodeMeaning and next step
429rate_limited

An anonymous, signed-request or API-key allowance was exceeded.

Wait at least Retry-After seconds. Share a queue and retry budget across your workers.

500internal_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.

503busy

The application has reached a temporary concurrency or database limit.

Wait Retry-After seconds and retry with bounded backoff.