Device Flow

Authenticate users from CLI tools and devices without a browser


The Device Flow (RFC 8628) lets users authorize your app from a separate device with a browser. This is ideal for CLI tools, IoT devices, and environments without direct browser access.

Flow Overview#

  1. Your app requests a device code from Orshot
  2. Orshot returns a user_code and a verification URL
  3. You display the code and URL to the user
  4. The user visits the URL in a browser, enters the code, and approves access
  5. Meanwhile, your app polls the token endpoint until the user completes authorization

Step 1: Request a Device Code#

curl -X POST https://api.orshot.com/v1/oauth/device/code \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "scope": "workspace:read render:generate"
  }'

Response:

{
  "device_code": "device-code-string",
  "user_code": "A1B2-C3D4",
  "verification_uri": "https://orshot.com/oauth/device",
  "verification_uri_complete": "https://orshot.com/oauth/device?user_code=A1B2-C3D4",
  "expires_in": 600,
  "interval": 5
}
FieldDescription
device_codeUsed by your app to poll for the token (keep this secret)
user_codeDisplayed to the user — they enter this on the verification page
verification_uriThe URL the user visits to enter the code
verification_uri_completeURL with the code pre-filled (for QR codes, clickable links)
expires_inHow long the codes are valid (600 seconds = 10 minutes)
intervalMinimum seconds between polling requests

Step 2: Display the Code#

Show the user the code and where to go:

To authorize this app, visit:
  https://orshot.com/oauth/device

And enter the code:
  A1B2-C3D4

Or provide the verification_uri_complete as a clickable link or QR code.

Step 3: Poll for Tokens#

While the user is authorizing, poll the token endpoint at the specified interval:

curl -X POST https://api.orshot.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "client_id": "YOUR_CLIENT_ID",
    "device_code": "device-code-string"
  }'

While waiting (user hasn't approved yet):

{
  "error": "authorization_pending"
}

After the user approves:

{
  "access_token": "ost_...",
  "refresh_token": "osr_...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "workspace:read render:generate",
  "user_id": "user-uuid",
  "workspace_ids": ["workspace-uuid-1"]
}

Polling Errors#

ErrorMeaningAction
authorization_pendingUser hasn't approved yetKeep polling at the specified interval
slow_downYou're polling too fastIncrease your polling interval by 5 seconds
expired_tokenThe device code has expiredStart over from Step 1
access_deniedUser denied the requestShow an error to the user

Full Example (Node.js CLI)#

const CLIENT_ID = process.env.ORSHOT_CLIENT_ID;

async function authenticate() {
  // Step 1: Get device code
  const codeRes = await fetch("https://api.orshot.com/v1/oauth/device/code", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      client_id: CLIENT_ID,
      scope: "workspace:read render:generate",
    }),
  });
  const { device_code, user_code, verification_uri, interval } =
    await codeRes.json();

  // Step 2: Show the user
  console.log(`\nVisit: ${verification_uri}`);
  console.log(`Enter code: ${user_code}\n`);
  console.log("Waiting for authorization...");

  // Step 3: Poll for tokens
  let pollInterval = interval * 1000;

  while (true) {
    await new Promise((r) => setTimeout(r, pollInterval));

    const tokenRes = await fetch("https://api.orshot.com/v1/oauth/token", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        grant_type: "urn:ietf:params:oauth:grant-type:device_code",
        client_id: CLIENT_ID,
        device_code,
      }),
    });

    const data = await tokenRes.json();

    if (data.access_token) {
      console.log(
        "Authorized! Access granted to workspaces:",
        data.workspace_ids,
      );
      return data;
    }

    if (data.error === "slow_down") {
      pollInterval += 5000;
    } else if (data.error !== "authorization_pending") {
      throw new Error(`Authorization failed: ${data.error}`);
    }
  }
}

User Experience Tips#

  • Always show verification_uri_complete when possible — saves users from typing the code
  • Display a QR code linking to verification_uri_complete for mobile-friendly approval
  • Show a clear "Waiting for authorization..." message while polling
  • Handle expired_token gracefully — offer to restart the flow

All Set? Let's Start Automating

Get Your API Key →
  • Image, PDF and Video Generation via API
  • Canva like editor with AI and smart features
  • No-Code Integrations (Zapier, Make, n8n etc.)
  • Embed Orshot Studio in your app
  • Start Free. No credit card required. Cancel anytime.