Device authorization
Exchange a device code for an API key without a redirect URL, following RFC 8628.
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