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 Tuple

POST /api/v1/fga/tuples

Create a single authorization relationship (tuple). To create multiple tuples, call this endpoint once per tuple.

Headers

  • Authorization: Bearer <apiKey>
  • X-Bulwark-Tenant: <tenant-id>

Body

{
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "owner",
  "object_type": "document",
  "object_id": "doc_abc"
}

All five fields are required. Valid relations: owner, editor, viewer, can_read, can_write, member, parent.

Response 201

Returns the written tuple:

{
  "id": "tpl_01j...",
  "tenant_id": "019d...",
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "owner",
  "object_type": "document",
  "object_id": "doc_abc",
  "created_at": "2026-03-20T10:00:00Z"
}

Delete Tuple

DELETE /api/v1/fga/tuples

Delete a single authorization relationship (tuple). To delete multiple tuples, call this endpoint once per tuple.

Body

{
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "owner",
  "object_type": "document",
  "object_id": "doc_abc"
}

All five fields are required and must exactly match the tuple to be deleted.

Response 200

{ "status": "deleted" }

List Tuples

GET /api/v1/fga/tuples

Returns all tuples stored for the tenant, optionally filtered by object.

Query Parameters

  • object_type — Optional. Filter to tuples for this object type (e.g. document).
  • object_id — Optional. Filter to tuples for this specific object ID.
  • offset — Pagination offset (default: 0).
  • limit — Results per page (default: 50).

Response 200

{
  "tuples": [
    {
      "id": "tpl_01j...",
      "tenant_id": "019d...",
      "user_type": "user",
      "user_id": "usr_01j",
      "relation": "owner",
      "object_type": "document",
      "object_id": "doc_abc",
      "created_at": "2026-03-20T10:00:00Z"
    }
  ],
  "total": 1,
  "offset": 0,
  "limit": 50
}

Check Access

POST /api/v1/fga/check

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

Body

{
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "viewer",
  "object_type": "document",
  "object_id": "doc_abc"
}

All five fields are required.

Response 200

{
  "allowed": true,
  "reason": "direct"
}

reason is omitted when empty. allowed: false means the principal does not have the requested relation.


Filter Objects

POST /api/v1/fga/filter

Given a list of object IDs, return only those the user has the requested relation on.

Body

{
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "viewer",
  "object_type": "document",
  "object_ids": ["doc_abc", "doc_xyz", "doc_123"]
}

user_type, user_id, relation, and object_type are required. object_ids is the list of bare IDs to test.

Response 200

{
  "allowed_ids": ["doc_abc", "doc_123"],
  "total": 3,
  "allowed": 2,
  "denied": 1
}

List Objects

POST /api/v1/fga/list-objects

Return all object IDs of a given type that the user has a specific relation to.

Body

{
  "user_type": "user",
  "user_id": "usr_01j",
  "relation": "viewer",
  "object_type": "document",
  "limit": 50,
  "offset": 0
}

user_type, user_id, relation, and object_type are required. limit and offset are optional (default: no limit / 0).

Response 200

{
  "object_ids": ["doc_abc", "doc_123"],
  "total": 2
}