Skip to main content

Permission Filtering

All API endpoints support an optional userId parameter that scopes requests to what a specific Foundry VTT user can see and do. When omitted, the API operates with full GM-level access.

How It Works

Pass userId as a query parameter or in the request body:

GET /entity/get?clientId=fvtt_3a9f1c2e4b7d8e0f&uuid=Actor.xyz&userId=playerUserId

The module resolves the user and checks Foundry's built-in document permission system before returning data or performing operations.

Auto-injection via Scoped API Keys

Both clientId and userId can be bound to a scoped API key so callers never need to pass them explicitly:

  • Client scoping — a key bound to one or more clientIds auto-resolves clientId on every request. A single-client key always targets that world; no clientId parameter needed.
  • User scoping — a key can carry a scopedUserId (global, applied to every client) or per-client user IDs. When set, the relay injects userId into the request after clientId resolves. Callers send no userId at all.

This is the recommended approach for player-facing integrations: create a key scoped to the target world and the player's Foundry user ID. The integration never deals with clientId or userId bookkeeping.

# Key is scoped to fvtt_3a9f1c2e4b7d8e0f and userId=playerOne — no params needed
curl "https://your-relay/entity/get?uuid=Actor.xyz" \
-H "x-api-key: PLAYER_SCOPED_KEY"

User Resolution

The userId parameter (whether passed explicitly or injected by the key) accepts either:

  1. Foundry User ID (tried first) - e.g., userId=abc123def456
  2. Username (fallback, case-insensitive) - e.g., userId=PlayerOne

If the user is not found, the API returns an error: "User not found: <userId>".

Permission Levels

Foundry VTT has four document permission levels. The API respects these when userId is provided:

LevelRead BehaviorWrite Behavior
NONEDocument excluded from resultsOperation denied
LIMITEDMinimal data returned (name, uuid, type, img)Operation denied
OBSERVERFull document data returnedOperation denied
OWNERFull document data returnedOperation allowed

Read Endpoints

For read operations (GET entity, search, structure, etc.):

  • Single document: Returns full data, limited data, or a permission error depending on the user's permission level
  • Collections: Documents are filtered — NONE-permission documents are excluded, LIMITED documents return minimal data, OBSERVER+ documents return full data

Write Endpoints

For write operations (update, delete, create, give, remove, kill, etc.):

  • Requires OWNER permission on the target document
  • Returns a descriptive error if the user lacks permission

GM-Only Operations

Some operations require the user to be a GM regardless of document permissions:

EndpointReason
POST /execute-jsArbitrary JavaScript execution
POST /select, GET /selectedCanvas token control
POST /start-encounter, POST /end-encounterCombat management
POST /next-turn, POST /next-round, etc.Combat navigation
POST /create-folder, DELETE /delete-folderFolder management

File System Permissions

File system endpoints check Foundry's user permissions:

EndpointRequired Permission
GET /file-systemFILES_BROWSE
GET /downloadFILES_BROWSE
POST /uploadFILES_UPLOAD

Examples

Reading an entity as a player

# Player has OBSERVER permission on this actor — returns full data
curl "https://your-api/api/entity/get?clientId=abc&uuid=Actor.xyz&userId=playerOne"

# Player has LIMITED permission — returns only name, uuid, type, img
curl "https://your-api/api/entity/get?clientId=abc&uuid=Actor.npc123&userId=playerOne"

# Player has no permission — returns permission error
curl "https://your-api/api/entity/get?clientId=abc&uuid=Actor.secret&userId=playerOne"

Writing as a player

# Player owns this actor — update succeeds
curl -X PUT "https://your-api/api/entity/update?clientId=abc&uuid=Actor.xyz&userId=playerOne" \
-H "Content-Type: application/json" \
-d '{"data": {"name": "New Name"}}'

# Player doesn't own this actor — returns permission error
curl -X PUT "https://your-api/api/entity/update?clientId=abc&uuid=Actor.npc123&userId=playerOne" \
-H "Content-Type: application/json" \
-d '{"data": {"name": "New Name"}}'

GM-only operation with non-GM user

# Non-GM user tries to execute JavaScript — returns error
curl -X POST "https://your-api/api/utility/execute-js?clientId=abc&userId=playerOne" \
-H "Content-Type: application/json" \
-d '{"script": "return game.actors.size"}'
# Error: "User 'PlayerOne' must be a GM to execute JavaScript"

Backward compatibility (no userId)

# No userId — full GM-level access (same as before)
curl "https://your-api/api/entity/get?clientId=abc&uuid=Actor.secret"
# Returns full data regardless of permissions