SignEnvoy docs

Device-flow sign-in

A client that cannot hold a browser session (a terminal, a CI runner, a device) asks for a short code; a person approves it in the dashboard; the client receives a freshly minted API key. The shape follows RFC 8628. No plaintext key is ever stored on the server: approval records intent, and the key is minted in the same transaction that the client's claim consumes the code.

1. Ask for a code

Unauthenticated. client names the software asking; mode and name describe the key the person will be asked to approve.

curl -s -X POST https://signenvoystaging.fly.dev/v1/device/code \
  -H "Content-Type: application/json" \
  -d '{"client": "my-tool", "client_version": "1.0.0", "mode": "test", "name": "laptop"}'
{
  "device_code": "...",
  "user_code": "BCDF-GHJK",
  "verification_uri": "https://app.signenvoy.com/device",
  "verification_uri_complete": "https://app.signenvoy.com/device?code=BCDF-GHJK",
  "expires_in": 600,
  "interval": 5
}

Show the person user_code and open verification_uri_complete for them. The code uses letters only, without vowels or look-alikes, so it survives being read aloud.

2. The person approves

The dashboard page shows exactly what is being authorized before the button: the requesting client and version, the key's mode (test keys capture email and watermark sealed PDFs; live keys send real email and count toward the allowance), the key's name, and the workspace it will belong to. Any member of a Pro workspace may approve. A Personal workspace cannot hold API keys; its page says so, and the waiting client receives device-access-denied.

3. Poll for the key

curl -s -X POST https://signenvoystaging.fly.dev/v1/device/token \
  -H "Content-Type: application/json" \
  -d '{"device_code": "..."}'

Poll every interval seconds. Until the person acts, the answer is a problem:

Once approved, the next poll returns the key exactly once:

{
  "api_key": "senv_test_...",
  "key_id": "...",
  "display": "senv_test_…ab12",
  "mode": "test",
  "name": "laptop",
  "tenant_slug": "acme"
}

Store it where your platform keeps secrets; the dashboard lists it under API keys like any other, and it can be revoked there or with POST /v1/keys/{id}/revoke.

Limits

Both routes are unauthenticated and throttled per client address (sustained 1 request per second, burst 10); past that they answer rate-limited with Retry-After. Codes expire after ten minutes.