Connected Apps / Dynamic Client Registration

Register OAuth 2.0 clients dynamically and manage them via the admin API.


Dynamic Client Registration

POST /oauth2/register

Register a new OAuth 2.0 client following RFC 7591. This endpoint is used by applications that need to become first-class OAuth clients within Bulwark — receiving their own client_id and client_secret — so that agents can authenticate on their behalf.

Headers

  • X-Bulwark-Tenant: <tenant-id>
  • Authorization: Bearer <initial-access-token> — Optional. Required if DCR is restricted by tenant policy.

Body

{
  "client_name": "Acme Data Exporter",
  "redirect_uris": ["https://app.acme.com/auth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read:customers write:reports",
  "token_endpoint_auth_method": "client_secret_basic",
  "application_type": "web",
  "contacts": ["[email protected]"],
  "logo_uri": "https://app.acme.com/logo.png",
  "client_uri": "https://app.acme.com"
}

| Field | Required | Description | |-------|----------|-------------| | client_name | Yes | Human-readable application name | | redirect_uris | Yes (for authorization_code) | Allowed redirect URIs | | grant_types | No | Defaults to ["authorization_code"]. Also supports client_credentials. | | response_types | No | Defaults to ["code"] | | scope | No | Requested OAuth scopes | | token_endpoint_auth_method | No | client_secret_basic, client_secret_post, or none |

Response 201

{
  "client_id": "bwk_...",
  "client_secret": "...",
  "client_name": "Acme Data Exporter",
  "redirect_uris": ["https://app.acme.com/auth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "scope": "read:customers write:reports",
  "token_endpoint_auth_method": "client_secret_basic",
  "client_id_issued_at": 1743292800,
  "registration_access_token": "..."
}

Important: client_secret is returned only once. Store it securely.


List Clients (Admin)

GET /api/v1/oauth2/clients

Headers

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

Response 200

{
  "clients": [
    {
      "id": "01j...",
      "client_id": "bwk_...",
      "name": "Acme Data Exporter",
      "redirect_uris": ["https://app.acme.com/auth/callback"],
      "grant_types": ["authorization_code", "refresh_token"],
      "scopes": ["openid", "profile", "read:customers", "write:reports"],
      "is_public": false,
      "token_endpoint_auth_method": "client_secret_basic",
      "status": "active",
      "created_at": "2026-03-30T00:00:00Z",
      "updated_at": "2026-03-30T00:00:00Z"
    }
  ],
  "count": 1
}

Get Client (Admin)

GET /api/v1/oauth2/clients/{id}

Retrieve a single OAuth 2.0 client by its internal row ID. The tenant is verified server-side — clients that belong to another tenant return 404.

Headers

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

Path Parameters

| Parameter | Description | |-----------|-------------| | id | Internal row ID of the client (the id field, not client_id) |

Response 200

{
  "id": "01j...",
  "client_id": "bwk_...",
  "name": "Acme Data Exporter",
  "description": "Exports customer data to the Acme warehouse",
  "redirect_uris": ["https://app.acme.com/auth/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "scopes": ["openid", "profile", "read:customers", "write:reports"],
  "is_public": false,
  "token_endpoint_auth_method": "client_secret_basic",
  "status": "active",
  "created_at": "2026-03-30T00:00:00Z",
  "updated_at": "2026-03-30T00:00:00Z"
}

description is omitted when empty. client_secret is never returned by this endpoint.

Response 404

{ "error": "client not found" }

Create Client (Admin)

POST /api/v1/oauth2/clients

Create a client directly without an initial access token. Equivalent to DCR but requires admin credentials.

Headers

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

Body

Same schema as POST /oauth2/register.

Response 201

Same schema as the DCR 201 response.


Revoke Client

DELETE /api/v1/oauth2/clients/{id}

Immediately revokes the client and all tokens issued to it. Active sessions authenticated via this client will be terminated.

Headers

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

Response 200

{
  "status": "deleted"
}