---
title: Public API and documentation
description: One OpenAPI contract feeding Blume Pages, Scalar, and OpenWiki.
---

Outward-facing platform surface for **BLI-66**: documented REST API, scoped tokens, webhook subscriptions, and a permission-filtered OData v4 BI service. Linear issue **BLI-66** owns product scope; the committed OpenAPI artifact owns the currently published HTTP contract.

## Entry URLs

| Surface | Where |
| --- | --- |
| Partner guides (this site) | [/partner](/partner) · Pages: https://supreme-adventure-r2rn151.pages.github.io/partner |
| Developer map (this site) | [/developers](/developers) |
| OpenAPI JSON (Pages) | [/openapi.json](/openapi.json) — primary published reference |
| OpenAPI JSON (app) | `GET /api/v1/openapi.json` |
| Scalar reference (deployed staff app) | `GET /reference` — see [Scalar API reference](/developers/scalar) for the auth reality and runtime-vs-committed distinction |
| Scalar local preview (developers only) | `http://localhost:3000/reference` after `pnpm dev` |

## Contract source of truth

Nothing below is a second source — all consumers take the same artifact:

1. Zod schemas in `packages/api-contract/src/`
2. → OpenAPI 3.1 via `@asteasolutions/zod-to-openapi`
3. → committed `packages/api-contract/openapi.json` (`pnpm --filter @blinqx/api-contract gen`)
4. → live `GET /api/v1/openapi.json` on `apps/web`

## Four layers over one spec

```mermaid
flowchart LR
  zod["packages/api-contract zod"] --> gen["openapi.json"]
  gen -->|symlink| blume["apps/docs Blume + Pages"]
  gen -->|symlink| wiki["openwiki"]
  gen -->|runtime generateOpenApiSpec| web["apps/web /api/v1/openapi.json"]
  web --> scalar["apps/web /reference Scalar"]
  blume --> pagesJson["Pages /openapi.json"]
```

Caption: one generated contract; Blume and OpenWiki symlink it; Scalar renders the live app generator.

## Symlinks (committed)

```text
apps/docs/openapi.json              → packages/api-contract/openapi.json
apps/docs/public/openapi.json       → packages/api-contract/openapi.json
openwiki/integrations/openapi.json  → packages/api-contract/openapi.json
```

Verify: [`docs/runbooks/docs-surfaces.md`](https://github.com/blinqx-hq/basenet-rebuild-poc/blob/main/docs/runbooks/docs-surfaces.md).

## Partner narrative

Guides under [/partner](/partner) cover scoped tokens, scopes/`can()`, pagination, idempotency (not yet in the contract), rate limits, webhook subscriptions, and OData.

## Published capability map

| Capability | Published paths | Scope | Contract boundary |
| --- | --- | --- | --- |
| REST resources | `/api/v1/matters`, `/relations`, `/documents`, `/hours`, `/tasks`, `/invoices` | Resource-specific read/write scopes; invoices currently publish `billing:read` only | JSON request and response schemas come from OpenAPI |
| Webhook subscriptions | `GET` and `POST /api/v1/webhooks` | `webhooks:admin` | List/create and the five event types are published; delivery headers, retry timing, update, and revoke are not yet in OpenAPI |
| OData BI | `/api/v1/bi`, `/api/v1/bi/$metadata`, `/api/v1/bi/matters`, `/hours`, `/billing` | `bi:read` | OData v4 discovery and collection envelopes with `$top`/`$skip`; no general query-option or DirectQuery promise |

The table deliberately follows `packages/api-contract/openapi.json`. A route that exists in `apps/web` but is absent from that artifact is an internal or not-yet-published route.

## Authentication and permission flow

```mermaid
sequenceDiagram
  participant Partner as Partner client
  participant Route as apps web API route
  participant Auth as API authentication
  participant Permission as can permission engine
  participant Handler as Application handler
  Partner->>Route: Bearer request under api v1
  Route->>Auth: Validate token and required scope
  Auth-->>Route: Tenant and resolved actor
  Route->>Permission: Check action and resource
  Permission-->>Route: Allow decision and rule
  Route->>Handler: Execute scoped command or query
  Handler-->>Route: Permission filtered result
  Route-->>Partner: JSON response and audit metadata
```

Caption: token scope is the coarse gate; the resolved user's normal permission rule remains authoritative before serialization.

## Webhook subscription contract

`POST /api/v1/webhooks` accepts an event type, callback URL, secret, and optional active flag. The transport adapter authenticates `webhooks:admin`; the webhook capability layer adds the tenant-admin and callback safety gates; the application handler persists the subscription without returning the secret.

```mermaid
flowchart TD
  request["POST webhook subscription"] --> scope["Require webhooks admin scope"]
  scope --> admin["Require tenant administrator"]
  admin --> safety["Validate callback destination"]
  safety --> create["Create secret protected subscription"]
  create --> response["Return subscription without secret"]
```

Caption: the public create path applies scope, permission, and callback-safety checks before persistence.

The published event enum is `matter.created`, `relation.created`, `hour.created`, `task.created`, and `invoice.sent`. Keep partner docs limited to fields and operations present in OpenAPI; internal delivery implementation is not a substitute for a published signature/retry contract.

## OData discovery and paging

The BI service is intentionally small and read-only. An OData-aware client starts at `/api/v1/bi`, reads `/api/v1/bi/$metadata`, then requests the `matters`, `hours`, or `billing` entity set. Collection routes reuse the API's permission filtering before paging and return `@odata.context`, `value`, and optional `@odata.nextLink`.

```mermaid
sequenceDiagram
  participant BI as BI client
  participant Service as API v1 BI service
  participant Metadata as EDMX metadata
  participant Entity as Permission filtered entity set
  BI->>Service: GET service document
  Service-->>BI: matters hours billing
  BI->>Metadata: GET dollar metadata
  Metadata-->>BI: OData v4 CSDL
  BI->>Entity: GET matters with top and skip
  Entity-->>BI: OData context value next link
```

Caption: OData discovery exposes three permission-filtered entity sets and offset paging without promising unsupported query options.

## Change and verification workflow

1. Change Zod schemas or operation registration under `packages/api-contract/src/`.
2. Regenerate the committed artifact with `pnpm --filter @blinqx/api-contract gen`.
3. Review the `packages/api-contract/openapi.json` diff; partner availability wording must follow that artifact.
4. Run `pnpm --filter @blinqx/api-contract test` and the route-focused tests for the affected surface.
5. Build the docs site so `/openapi.json`, partner guides, and this developer map are checked together.

## Hard constraints

- EU-only production path for tenant data, auth, AI, observability.
- Every tenant resource is permission-filtered through `can()` before serialization.
- Bearer tokens hashed at rest and scoped; `/api/v1` versioned; list endpoints server-side paged.
- Published availability comes from committed OpenAPI, not from an unregistered route or a future Linear acceptance criterion.
