> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powabase.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API conventions

> The shared patterns across Powabase's HTTP APIs: header conventions, naming, error envelopes, pagination, response shapes. Once you know these, every endpoint is easier to read.

Powabase exposes several HTTP surfaces: the agentic `/api/*`, PostgREST `/rest/v1/*`, GoTrue `/auth/v1/*`, Storage `/storage/v1/*`, and Realtime. They were built independently (most are upstream Supabase services Powabase ships) and have slightly different conventions. This page documents the shared patterns and where they diverge.

For specific endpoints, see the relevant `/api-reference/*` pages.

## Header conventions

Two headers on every authenticated request:

```
apikey: <Anon Key | Service Role Key>
Authorization: Bearer <Anon Key | Service Role Key | User Access Token>
```

The `apikey` header is required by Kong's `key-auth` plugin for routing. The `Authorization` header is what the downstream service (PostgREST, GoTrue, etc.) actually verifies for role assignment. They can be different values: `apikey: Anon` + `Authorization: Bearer <user-access-token>` is the standard browser-side pattern after sign-in.

For Realtime WebSocket connections, both pieces come in as query parameters: `?apikey=<KEY>&vsn=1.0.0`. WebSocket APIs in browsers don't let you set headers on the upgrade request.

## PUT vs PATCH for updates

The agentic `/api/*` surface is inconsistent here:

| Resource                   | Update method |
| -------------------------- | ------------- |
| `/api/agents/{id}`         | PATCH         |
| `/api/tools/{id}`          | PUT           |
| `/api/orchestrations/{id}` | PUT           |
| `/api/sources/{id}`        | PATCH         |
| `/api/workflows/{id}`      | PATCH         |

The intent was that PATCH does partial updates (body keys not present aren't changed) and PUT does full replacement, but in practice the platform's PUT endpoints also work as partial updates. The choice is historical, not principled.

PostgREST uses PATCH for partial updates and supports PUT only when you specify `Prefer: resolution=merge-duplicates` (which is really an upsert).

GoTrue uses PUT on `/user` and `/admin/users/{id}` — both partial.

Storage uses PUT to overwrite an object at the same path.

If you're writing client code, match what the docs say for each endpoint. Don't try to derive PUT-vs-PATCH from first principles.

## Path versioning

The BaaS services have versioned paths: `/auth/v1/`, `/rest/v1/`, `/storage/v1/`, `/realtime/v1/`. The agentic `/api/*` surface is **not** versioned in the path.

This isn't a hard product commitment, just where things stand today. If the agentic surface needs versioning later, the most likely move is to introduce `/api/v2/*` alongside the existing `/api/*`. To future-proof, build a tiny indirection layer in your client code that constructs the URL from a constant.

## Resource naming

Most resources use plural-noun URLs:

* `/api/agents/{id}` (not `/api/agent/{id}`)
* `/api/knowledge-bases/{id}` (with a hyphen)
* `/api/ai-provider-keys` (also hyphenated)

A handful of endpoints use snake\_case in the path:

* `/api/ai-provider-keys/platform_supported` (underscore; this is the one most people get wrong)
* `/api/config/kb-defaults` (hyphen)

Match the docs literally. The platform's routing is case- and character-sensitive.

## Error envelopes

The agentic `/api/*` surface uses `{"error": "<message>"}` as the standard error envelope. Some endpoints add `code` or `error_code` for machine-readable identifiers:

```json theme={null}
{ "error": "Webhook not found", "error_code": "WORKFLOW_NOT_FOUND" }
```

```json theme={null}
{ "error": "BYOK key decrypt failed", "code": "provider_key_decrypt_failed", "provider": "openai" }
```

PostgREST uses a different shape, a JSON object with `code`, `message`, `details`, `hint`:

```json theme={null}
{
  "code": "23505",
  "message": "duplicate key value violates unique constraint",
  "details": "Key (email)=(alice@example.com) already exists.",
  "hint": null
}
```

The `code` is the Postgres SQLSTATE (5-character class).

GoTrue uses yet another shape: `{"error": "code", "error_description": "..."}` for OAuth-style errors and `{"msg": "..."}` for some validation errors:

```json theme={null}
{ "error": "invalid_grant", "error_description": "Invalid login credentials" }
```

Storage's error shape is `{"statusCode", "error", "message"}`:

```json theme={null}
{ "statusCode": "400", "error": "InvalidRequest", "message": "..." }
```

This diversity is unfortunate. If you're writing a unified error handler in your client, check `response.status` first, then try `.error || .message || .error_description || .msg` in that order.

## Pagination

PostgREST uses HTTP `Range` headers for offset pagination and the `Content-Range` response header for the total count:

```
Range: 0-19
→ Content-Range: 0-19/247
```

The agentic `/api/*` surface uses query string pagination with explicit `limit` and `offset` params:

```bash theme={null}
GET /api/agents?limit=20&offset=0
→ { "agents": [...], "total": 47, "limit": 20, "offset": 0 }
```

Defaults vary per endpoint, usually `limit=50` with a cap of 200.

For listing endpoints that return many items (KB sources, source page texts), prefer the agentic endpoints' explicit pagination. The PostgREST `Range` approach is fine but easier to forget.

## Response shapes for lists

Inconsistency worth knowing about. List endpoints across the agentic surface use a wrapped shape:

```json theme={null}
{ "agents": [...], "total": N, "limit": L, "offset": O }
```

But not always. Some return a bare array. Some return `{ items: [...], total, ... }`. The audit-flagged-and-corrected wrong examples in PR A (#8) were spots where this inconsistency tripped the docs themselves.

When in doubt, log the response and read its shape. The reference pages should show the actual shape; file an issue if you find one that doesn't.

## Idempotency

The agentic `/api/*` surface does not honor an `Idempotency-Key` header (despite the convention being common). Idempotency is computed internally for billing charges (see [Billing model](/concepts/billing-model)) but the API itself doesn't deduplicate user-supplied retry headers.

In practice: retrying a `POST /api/agents/{id}/run/stream` from a timeout creates a second run. Build your retry logic to handle that, usually by waiting longer between retries or by checking session state before retrying.

PostgREST doesn't honor `Idempotency-Key` either. For inserts that need idempotency, use unique constraints + upserts (`Prefer: resolution=merge-duplicates`).

## Common headers worth knowing

A short list of headers that change behavior across multiple endpoints:

| Header                                      | Where            | Effect                                                       |
| ------------------------------------------- | ---------------- | ------------------------------------------------------------ |
| `Prefer: return=representation`             | PostgREST writes | Return inserted/updated rows in response body                |
| `Prefer: count=exact`                       | PostgREST reads  | Include total count in `Content-Range`                       |
| `Prefer: resolution=merge-duplicates`       | PostgREST POST   | Upsert semantics                                             |
| `Accept-Profile: <schema>`                  | PostgREST reads  | Target a non-public schema                                   |
| `Content-Profile: <schema>`                 | PostgREST writes | Same, for writes                                             |
| `Accept: application/vnd.pgrst.object+json` | PostgREST reads  | Return single object, not array; fail if not exactly one row |
| `x-upsert: true`                            | Storage upload   | Overwrite existing object                                    |
| `Range: 0-19`                               | PostgREST reads  | Offset-based slicing                                         |

## Authentication conventions across services

| Service                            | Auth shape                                                                  |
| ---------------------------------- | --------------------------------------------------------------------------- |
| Agentic `/api/*`                   | `apikey` + `Authorization: Bearer`                                          |
| PostgREST `/rest/v1/*`             | `apikey` + `Authorization: Bearer`                                          |
| GoTrue `/auth/v1/*`                | `apikey` + `Authorization: Bearer` (Anon Key OR user access token)          |
| Storage `/storage/v1/*`            | `apikey` + `Authorization: Bearer`                                          |
| Realtime `/realtime/v1/*` (WS)     | `?apikey=<KEY>` query parameter                                             |
| Realtime `/realtime/v1/api` (REST) | `apikey` + `Authorization: Bearer` headers                                  |
| `/api/webhooks/{id}`               | `Authorization: Bearer <webhook_secret>` or `?token=<secret>` — no `apikey` |

The webhook trigger endpoint is the odd one out: no `apikey`, only the webhook secret.

## Next steps

<CardGroup cols={2}>
  <Card title="Auth & Connection" href="/guides/auth-connection">
    Where the headers all come from and how to assemble them.
  </Card>

  <Card title="Glossary" href="/concepts/glossary">
    The terminology that goes alongside the conventions.
  </Card>

  <Card title="Common pitfalls" href="/concepts/common-pitfalls">
    The errors that come from getting the conventions wrong.
  </Card>

  <Card title="PostgREST advanced" href="/guides/postgrest-advanced">
    The `Prefer` and `Accept` header patterns that change PostgREST behavior.
  </Card>
</CardGroup>
