veil

Register an app

A person or an organisation owner signs the registration. A confidential app with client_secret_basic gets its secret once.

POST/v1/developer/accounts/{n}/oidc-clients
Access
Signed request
Client
Local or server client

Parameters#

NameLocationMeaning
nRequiredPathThe card that owns the apps: a person, or an organisation through an accepted owner.

JSON body#

NameLocationMeaning
nameRequiredJSON1 to 60 visible characters.
siteJSONhttps URL, at most 300 characters.
redirectUrisRequiredJSON1 to 10 exact URIs: https, or http on 127.0.0.1, [::1] or localhost (any port).
postLogoutRedirectUrisJSONUp to 10 URIs, same rules.
idTokenAlgJSONEdDSA (default) or ES256.
logoJSON{type, data}: a PNG or GIF in standard base64, at most 256 KiB and 512 px; null removes it.
typeRequiredJSONpublic (native or browser app, PKCE only) or confidential (a server).
authMethodJSONclient_secret_basic (default) or private_key_jwt, for a confidential app.
jwksJSONWith private_key_jwt: 1 to 8 public keys.
refreshJSONtrue (default) gives refresh tokens.

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
curl -fsS -X POST \
  -H "X-Public-Key: $PUBLIC_KEY" -H "X-Signature: $REQUEST_SIGNATURE" \
  --json @request.json "https://veil.rip/v1/developer/accounts/1/oidc-clients"

Response#

HTTP 201application/json

201 with {client, secret}.

FieldTypeMeaning
clientOidcClientThe app.
secretstring | nullveil_secret_…, shown once; null for a public or private_key_jwt app.

Expand a nested object for its fields. Types ending in null allow a null value; optional fields may be absent. Other fields are present in that object.

App OidcClient
FieldTypeMeaning
idstringclient_id.
namestringThe name the consent page shows.
sitestring | nullThe app's https site.
redirectUrisarray<string>Exact redirect URIs.
postLogoutRedirectUrisarray<string>Where /oidc/logout may send the browser.
typestringpublic or confidential.
authMethodstringnone, client_secret_basic or private_key_jwt.
idTokenAlgstringEdDSA or ES256.
refreshbooleanWhether the app gets refresh tokens.
logostring | nullThe logo's address, with ?v= its hash.
verifiedobject | null{domain}: the owner card proves the domain of the site and of every https redirect URI.
ownerobject | null{n, name, type, url} of the owner card; null for an app registered without a card.
dynamicbooleanRegistered through POST /oidc/register.
createdAtstringISO 8601 UTC.
expiresAtstring | nullA dynamic app's end, 30 days after registration.
lastUsedAtstring | nullLast sign-in or token.
jwksobject | nullThe public keys of a private_key_jwt app.

Response headers#

HeaderValueMeaning
Content-Typeapplication/jsonJSON, also for errors.
Cache-Controlno-storePrivate: never cached.

Usage notes#

  • At most 512 KiB, for the logo. Unknown fields are refused.

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_json

The body is not a JSON object, contains duplicate keys or has trailing data.

Send one valid JSON object with Content-Type application/json.

400invalid_parameter

A path, query or body field is missing, malformed or outside its accepted range.

Correct the field named in error.message before sending again.

403identity_banned

The acting identity is suspended by Veil staff.

Wait for the restriction to end or ask staff to review it.

403key_mismatch

The signing key does not have authority over this card or operation.

Use an active device key with the required owner or member role.

404not_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.

409oidc_client_limit

The card has 20 apps already.

Delete an app before registering another.

413payload_too_large

The request body exceeds this endpoint’s byte limit.

Reduce the encoded body. API-key management accepts at most 8 KiB.

422invalid_client_metadata

An app's field on /developers is missing or not allowed.

Fix the field named in error.message.

422invalid_redirect_uri

A redirect URI on /developers is not https, or http outside the loopback interface.

Fix the URI named in error.message.

Shared request errors
HTTPCodeMeaning and next step
400invalid_nonce

The signed-request nonce has an invalid format.

Generate a fresh 16 to 128-character nonce using letters, digits, _ or -.

400invalid_signature_alg

X-Signature-Alg is unsupported.

Use a supported signing algorithm and matching key/signature encoding.

400invalid_timestamp

The signed-request timestamp is not Unix milliseconds.

Use an integer Unix timestamp in milliseconds and sign the corrected request.

401invalid_signature

The HTTP request signature does not verify.

Sign the exact method, path and body bytes, without changing them afterwards.

401missing_signature

The request requires a device signature and its headers are missing or unreadable.

Send X-Public-Key, X-Signature and the signed-request metadata. An API key does not replace a device signature.

401stale_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.

403key_banned

The device signing key is suspended by Veil staff.

Use an authorised active key or ask staff to review the restriction.

409replayed_nonce

The signing key already used this nonce.

Check whether the previous request succeeded before making a fresh signed request.

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.