FGA Endpoints

Fine-Grained Authorization — relationship-based access control for agents, users, and resources.


Authentication

FGA endpoints accept either a Bulwark-issued user JWT or an API key (bwk_live_* / bwk_test_*). Both are sent as Authorization: Bearer <token>. The server dispatches by token prefix: tokens beginning with bwk_ are validated against the API-key store; everything else is verified as a JWT.

X-Bulwark-Tenant is required on every FGA request and drives row-level isolation. An API key may only be used against the tenant it was issued for; a mismatch returns 403.

Required scopes

| Endpoint | JWT requirement | API-key scope | | --------------------------------------------------------------------------------------- | ---------------------------------------- | ------------- | | POST /api/v1/fga/check, POST /api/v1/fga/filter, POST /api/v1/fga/list-objects, GET /api/v1/fga/tuples | Any valid JWT for the tenant | fga:read | | POST /api/v1/fga/tuples, DELETE /api/v1/fga/tuples | JWT with admin role | fga:write |

JWT principals do not declare scopes — the role claim is the authorization signal. API-key principals carry an explicit scope set declared at key creation; missing the required scope returns 403 with the scope name in the body so callers can correct the key configuration.

API keys are created and scoped via the dashboard or POST /api/v1/api-keys (see API Key Endpoints). The set of valid scope strings is enumerable at GET /api/v1/api-keys/scopes.


Write Tuples

POST /api/v1/fga/tuples

Create authorization relationships (tuples).

Headers

  • Authorization: Bearer <apiKey>
  • X-Bulwark-Tenant: <tenant-id>
  • X-Bulwark-App-Id: <app-id> — Optional. Filters results to a specific application.

Body

{
  "writes": [
    {
      "user": "user:usr_01j",
      "relation": "owner",
      "object": "document:doc_abc"
    },
    {
      "user": "agent:agent_01j",
      "relation": "viewer",
      "object": "document:doc_abc"
    }
  ]
}

Response 200

{
  "data": {
    "written": 2
  }
}

Delete Tuples

DELETE /api/v1/fga/tuples

Body

{
  "deletes": [
    {
      "user": "agent:agent_01j",
      "relation": "viewer",
      "object": "document:doc_abc"
    }
  ]
}

Response 200

{
  "data": {
    "deleted": 1
  }
}

Check Access

POST /api/v1/fga/check

Check whether a user or agent has a specific relation to an object.

Body

{
  "user": "agent:agent_01j",
  "relation": "viewer",
  "object": "document:doc_abc"
}

Response 200

{
  "data": {
    "allowed": true
  }
}

Batch Check

POST /api/v1/fga/batch-check

Check multiple relationships at once.

Body

{
  "checks": [
    { "user": "agent:agent_01j", "relation": "viewer", "object": "document:doc_abc" },
    { "user": "agent:agent_01j", "relation": "editor", "object": "document:doc_abc" }
  ]
}

Response 200

{
  "data": {
    "results": [
      { "allowed": true },
      { "allowed": false }
    ]
  }
}

Filter Objects

POST /api/v1/fga/filter

Return only the objects from a list that a user has access to.

Body

{
  "user": "agent:agent_01j",
  "relation": "viewer",
  "type": "document",
  "objects": ["document:doc_abc", "document:doc_xyz", "document:doc_123"]
}

Response 200

{
  "data": {
    "allowed": ["document:doc_abc", "document:doc_123"]
  }
}

List Objects

POST /api/v1/fga/list-objects

Return all objects of a type that a user has a specific relation to.

Body

{
  "user": "agent:agent_01j",
  "relation": "viewer",
  "type": "document"
}

Response 200

{
  "data": {
    "objects": ["document:doc_abc", "document:doc_123"]
  }
}