Skip to main content
Exchange device code or refresh token for an access token.

Request

Method: POST Endpoint: /oauth/token
curl -X POST "https://api.noorle.com/oauth/token" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "ABCD1234EFGH5678",
    "client_id": "noorle-cli"
  }'

Request Body

Device Code Grant

{
  "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
  "device_code": "ABCD1234EFGH5678",
  "client_id": "noorle-cli"
}
FieldTypeRequiredDescription
grant_typestringYesMust be urn:ietf:params:oauth:grant-type:device_code
device_codestringYesDevice code from /device/authorize
client_idstringYesOAuth client ID

Refresh Token Grant

{
  "grant_type": "refresh_token",
  "refresh_token": "refresh_...",
  "client_id": "noorle-cli"
}
FieldTypeRequiredDescription
grant_typestringYesMust be refresh_token
refresh_tokenstringYesRefresh token from previous response
client_idstringYesOAuth client ID

Client Credentials Grant

{
  "grant_type": "client_credentials",
  "client_id": "noorle-sa-1234567890",
  "client_secret": "secret_abcdefg"
}
FieldTypeRequiredDescription
grant_typestringYesMust be client_credentials
client_idstringYesService account client ID
client_secretstringYesService account secret

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "refresh_...",
  "scope": "api"
}
FieldTypeDescription
access_tokenstringBearer token for API requests
token_typestringAlways “Bearer”
expires_inintegerSeconds until token expires (typically 1 hour)
refresh_tokenstringToken to refresh access_token
scopestringGranted scopes

Status Codes

CodeMeaning
200Success, token returned
400Invalid request parameters
401Invalid credentials
429Rate limited

Pending Responses

Authorization Pending

User hasn’t approved yet. Keep polling:
{
  "error": "authorization_pending",
  "error_description": "The user has not granted authorization yet"
}
Action: Retry after interval seconds.

Slow Down

Server request too many polls. Increase interval:
{
  "error": "slow_down",
  "error_description": "Polling too frequently. Increase interval."
}
Action: Increase polling interval by 5 seconds, retry.

Access Denied

User rejected the request:
{
  "error": "access_denied",
  "error_description": "User denied the authorization request"
}
Action: Request new device code and try again.

Expired Token

Device code or refresh token expired:
{
  "error": "expired_token",
  "error_description": "Device code expired. Request new authorization."
}
Action: Call /device/authorize again.

Examples

Device Code to Token

# 1. Get device code
DEVICE=$(curl -s -X POST https://api.noorle.com/oauth/device/authorize \
  -H "Content-Type: application/json" \
  -d '{"client_id": "noorle-cli"}')

DEVICE_CODE=$(echo $DEVICE | jq -r '.device_code')

# 2. User approves at verification_uri
# (shown to user)

# 3. Exchange for token
TOKEN=$(curl -s -X POST https://api.noorle.com/oauth/token \
  -H "Content-Type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:device_code\",
    \"device_code\": \"$DEVICE_CODE\",
    \"client_id\": \"noorle-cli\"
  }")

ACCESS=$(echo $TOKEN | jq -r '.access_token')
echo "Token: $ACCESS"

Refresh Token

# Original response included refresh_token
REFRESH=$(echo $TOKEN | jq -r '.refresh_token')

# Refresh when access_token expires
NEW_TOKEN=$(curl -s -X POST https://api.noorle.com/oauth/token \
  -H "Content-Type: application/json" \
  -d "{
    \"grant_type\": \"refresh_token\",
    \"refresh_token\": \"$REFRESH\",
    \"client_id\": \"noorle-cli\"
  }")

NEW_ACCESS=$(echo $NEW_TOKEN | jq -r '.access_token')

Service Account

# Service accounts use client_credentials grant
TOKEN=$(curl -s -X POST https://api.noorle.com/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "noorle-sa-1234567890",
    "client_secret": "secret_abcdefg"
  }')

ACCESS=$(echo $TOKEN | jq -r '.access_token')

Token Handling

Store Token Securely

# ❌ Don't hardcode
TOKEN="eyJhbGc..."

# ✅ Use files with restricted permissions
echo "$TOKEN" > ~/.noorle/token
chmod 600 ~/.noorle/token

# ✅ Use environment variables
export NOORLE_TOKEN="$TOKEN"

Refresh Before Expiry

import time
import json

def ensure_fresh_token():
    with open("~/.noorle/token.json") as f:
        token_data = json.load(f)

    # Refresh if expiry is within 5 minutes
    if time.time() + 300 > token_data["expires_at"]:
        new_token = refresh_token(token_data["refresh_token"])
        save_token(new_token)
        return new_token["access_token"]

    return token_data["access_token"]

Use Bearer Token

curl https://api.noorle.com/v1/capabilities \
  -H "Authorization: Bearer $ACCESS"

Token Lifetime

  • Access token: 1 hour
  • Refresh token: 30 days
  • Device code: 30 minutes
After access token expires:
  1. Use refresh_token to get new access_token
  2. If refresh_token expired, start device flow again