Docs


Device authorization

Exchange a device code for an API key without a redirect URL, following RFC 8628.

Last updated June 3, 2026

The device authorization endpoints let a CLI or device sign in on behalf of a user without a browser redirect. The flow matches RFC 8628: the device asks for a user code, the user approves on a separate device, and the device polls until it receives a key.

The Python SDK wraps this entire flow behind vilvik login. If you are building your own client or script in another language, call these endpoints directly.

Using the Python SDK?

Run vilvik login (or call vilvik.login()) instead of calling these endpoints by hand. See Signing in with vilvik login.

Step 1 - request a device and user code

POST /api/v1/auth/device/code

Public endpoint. No Authorization header required.

Request body

{}

No fields are required. Send an empty object or an empty body.

Response

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_aXG8XJLKE2S",
  "user_code": "ABCD-1234",
  "verification_uri": "https://vilvik.com/device/approve",
  "verification_uri_complete": "https://vilvik.com/device/approve?user_code=ABCD-1234",
  "expires_in": 1800,
  "interval": 5
}
Field What it is
device_code Opaque token your device uses when polling. Keep this secret.
user_code Short human-readable code the user types or scans at verification_uri.
verification_uri URL to display to the user. They open it and enter the user_code.
verification_uri_complete Same URL with user_code pre-filled. Use this in a QR code or a "click here" link when the display supports it.
expires_in How many seconds the codes are valid. After this the user must start over.
interval Minimum seconds between polling attempts. Polling faster than this returns slow_down.

Show the user the user_code and the verification_uri (or a link to verification_uri_complete). Then start polling Step 2.

Step 2 - poll for the token

POST /api/v1/auth/device/token

Public endpoint. No Authorization header required.

Request body

{
  "device_code": "GmRhmhcxhwAzkoEqiMEg_aXG8XJLKE2S"
}
Field Required What it is
device_code Yes The device_code from Step 1.

Success response (200)

When the user approves, the next poll returns:

{
  "api_key": "vlk_live_...",
  "scopes": ["imports:write"]
}
Field What it is
api_key A freshly issued key. Store it in your credentials file or secret manager.
scopes The scopes attached to the key. Device-flow keys are issued with imports:write.

Pending and error responses (400)

While waiting, or when something goes wrong, the server returns 400 with a JSON body:

{
  "error": "authorization_pending"
}
error value Meaning What to do
authorization_pending The user has not approved yet. Wait at least interval seconds and poll again.
slow_down You polled too fast. Add 5 seconds to your polling interval and keep polling.
expired_token The device_code has expired (past expires_in). Restart from Step 1.
access_denied The user declined the approval. Stop polling. Show an error or let the user try again from Step 1.

Example polling loop (Python)

import time
import requests

host = "https://vilvik.com"

# Step 1
resp = requests.post(f"{host}/api/v1/auth/device/code", json={})
data = resp.json()

device_code = data["device_code"]
interval = data["interval"]

print("Open this URL and approve:")
print(data["verification_uri_complete"])

# Step 2
while True:
    time.sleep(interval)
    poll = requests.post(
        f"{host}/api/v1/auth/device/token",
        json={"device_code": device_code},
    )
    body = poll.json()

    if poll.status_code == 200:
        api_key = body["api_key"]
        print(f"Got key: {api_key}")
        break

    error = body.get("error")
    if error == "slow_down":
        interval += 5
    elif error in ("expired_token", "access_denied"):
        print(f"Login failed: {error}")
        break
    # "authorization_pending" -> keep polling
Thanks for the feedback!