Skip to main content

Migrating to v3 Authentication

Version 3 introduces scoped API keys and a device-flow key request system. This guide covers what changed and how to update your integration.

What Changed

v2v3
Base URLfoundryvtt-rest-api-relay.fly.devfoundryrestapi.com
API key typeSingle master key, full accessScoped key with explicit permissions
Key distributionCopy/paste from dashboardDevice-flow: app requests, user approves in browser
Headerx-api-key: <master-key>x-api-key: <scoped-key> (same header)

The x-api-key header is unchanged — only the key value and how you obtain it are different.


The New Key Request Flow

Instead of asking users to manually copy a key from the dashboard, your app initiates a device-flow request and the user approves it in their browser. This is similar to OAuth device authorization.

Step 1 — Create a key request

POST https://foundryrestapi.com/auth/key-request
Content-Type: application/json

{
"appName": "My App",
"appDescription": "Brief description of what your app does",
"appUrl": "https://your-app-website.com",
"scopes": ["entity:read", "roll:execute", "clients:read"],
"clientIds": ["optional-client-id-to-scope-to"]
}

Response:

{
"code": "abc123",
"approvalUrl": "https://foundryrestapi.com/approve/abc123",
"expiresIn": 600,
"expiresAt": "2026-01-01T12:10:00Z"
}
  • appName and scopes are required. Everything else is optional.
  • clientIds pre-scopes the key to specific Foundry clients. Users can adjust this at approval time.
  • The request expires in 10 minutes.
  • No authentication is required for this endpoint.

Step 2 — Open the approval URL

Open approvalUrl in the user's browser. The user will be prompted to log in to their relay account (if not already) and then approve or deny the request. They can adjust the scopes and client bindings before approving.

Step 3 — Poll for the result

Poll GET /auth/key-request/{code}/status every 3–5 seconds until the status changes.

GET https://foundryrestapi.com/auth/key-request/abc123/status

Possible responses:

StatusMeaning
pendingUser hasn't acted yet — keep polling
approvedUser approved — response includes apiKey and scopes
deniedUser denied the request
expiredRequest timed out (10 min)
exchangedKey was already retrieved — don't poll again

Approved response:

{
"status": "approved",
"apiKey": "abc123...64hexchars",
"scopes": [
0: "entity:read",
1: "roll:execute",
2: "clients:read"
],
"clientIds": [
0: "world-abc123"
]
}

Store the apiKey securely. It will not be returned again.

Step 4 — Use the key

Pass the scoped key in the x-api-key header, exactly as before:

GET https://foundryrestapi.com/get?clientId=world-abc123&type=Actor
x-api-key: abc123...64hexchars

Available Scopes

Request only the scopes your app actually needs.

ScopeWhat it allows
entity:readRead actors, items, and other entities
entity:writeCreate, update, delete entities
roll:readRead recent roll results
roll:executeExecute rolls
chat:readRead chat messages
chat:writeSend chat messages
encounter:readRead combat encounter state
encounter:manageStart, end, advance encounters
macro:listList available macros
macro:executeExecute macros
macro:writeCreate, update, delete macros
scene:readRead scene data and screenshots
scene:writeModify scenes
canvas:readRead canvas/token state
canvas:writeModify canvas/tokens
effects:readRead active effects
effects:writeModify active effects
user:readRead Foundry user list
user:writeModify Foundry users
file:readDownload files from Foundry
file:writeUpload files to Foundry
playlist:controlControl playlists and sounds
world:infoRead world/system metadata
clients:readList connected Foundry clients
sheet:readRead and screenshot character sheets
events:subscribeSubscribe to roll/chat SSE streams
session:manageManage headless browser sessions
searchSearch entities via QuickInsert
structure:readRead folder/compendium structure
structure:writeModify folder structure
dnd5eD&D 5e system-specific endpoints
execute-jsExecute arbitrary JavaScript in Foundry
caution

execute-js grants full arbitrary code execution inside Foundry. Only request it if absolutely necessary, and users should understand what they are approving.


Example Implementation (Python)

This mirrors the pattern used in the Foundry REST API MIDI Integration.

import requests
import time
import webbrowser

RELAY_URL = "https://foundryrestapi.com"
APP_NAME = "My App"
APP_SCOPES = ["entity:read", "roll:execute", "clients:read"]

def request_api_key():
# Step 1: Create key request
resp = requests.post(f"{RELAY_URL}/auth/key-request", json={
"appName": APP_NAME,
"appDescription": "Reads entities and executes rolls",
"scopes": APP_SCOPES,
})
resp.raise_for_status()
data = resp.json()

code = data["code"]
approval_url = data["approvalUrl"]
expires_in = data.get("expiresIn", 600)

# Step 2: Open browser
print(f"Opening browser for approval (code: {code})")
webbrowser.open(approval_url)

# Step 3: Poll for result
start = time.time()
while time.time() - start < expires_in:
time.sleep(3)
status_resp = requests.get(f"{RELAY_URL}/auth/key-request/{code}/status")
status_data = status_resp.json()
status = status_data.get("status")

if status == "approved":
api_key = status_data["apiKey"]
print(f"Approved! Scopes: {status_data['scopes']}")
return api_key
elif status == "denied":
raise Exception("Key request was denied by the user")
elif status in ("expired", "exchanged"):
raise Exception(f"Key request ended with status: {status}")
# status == "pending" → keep polling

raise Exception("Key request timed out")

Web App Flow (Callback URL)

If your app runs in a browser or has a server that can receive a redirect, pass a callbackUrl in the initial request. After the user approves, the relay will redirect to:

https://your-app.com/callback?code=<exchange_code>

Exchange the code for the API key:

POST https://foundryrestapi.com/auth/key-request/exchange
Content-Type: application/json

{
"code": "<exchange_code>"
}

Response:

{
"apiKey": "abc123...64hexchars",
"scopes": [
0: "entity:read"
],
"clientIds": []
}

The exchange code is single-use.


Self-Hosted Relays

Everything above applies equally to self-hosted relays. Replace foundryrestapi.com with your relay's URL. Users approve requests against their own account on that relay.