Skip to content

REST API

A guided tour of the non-ingestion REST surface: the routes that are actually mounted in the Koa app, grouped by module, with the auth principal and role each enforces.

The ingestion surface (POST /v1/ingest/*) uses a different principal and a request-signing scheme; it has its own page, Ingestion and signing. The machine-readable contract is the interactive reference and the downloadable openapi.yaml.

Base URL and envelope

Production base URL is https://api.bainquet.online/v1. Every response uses the standard envelope; clients branch on ok, not on HTTP status. List reads serialize data as a bare array with the next-page cursor in meta.cursor. 204 No Content carries no body. See Envelope and errors for the full shape and the error-code catalogue.

Auth principals and roles

PrincipalHow it authenticates
UserAuthorization: Bearer <access JWT> (EdDSA/RS256, ~15 min), refreshed via the bnq_refresh httpOnly cookie
Connectorscoped bearer + HMAC (Ingestion and signing)
API keybnq_live_... via Authorization: Bearer or X-Api-Key; created under /v1/organizations/{id}/api-keys

Per-route enforcement happens inside handlers via requireUser and requireRole; the service layer additionally derives the effective per-organization role and enforces tenant scope. The RBAC ladder, low to high, is viewer < editor < organization_admin < organization_owner < admin < super_admin.

System probes Stable

MethodPathAuthNotes
GET/healthznoneliveness, no I/O
GET/readyznonereadiness; round-trips Postgres; 503 service.unavailable if not ready
GET/metricsnonePrometheus-format metrics, plain text (not the JSON envelope)
GET/noneservice banner

Auth (/v1/auth)

Fully implemented against a Postgres repo.

MethodPathPrincipalStatus
POST/v1/auth/registeranonymous201
POST/v1/auth/verify-emailanonymous200
POST/v1/auth/loginanonymous200 (sets bnq_refresh cookie)
POST/v1/auth/googleanonymous200 (popup ID-token sign-in)
POST/v1/auth/refreshrefresh cookie or body200 (rotated)
POST/v1/auth/logoutuser204
POST/v1/auth/logout-alluser204
GET/v1/auth/meuser200
POST/v1/auth/password/changeuser204 (bumps token version, revokes sessions)
POST/v1/auth/password/forgotanonymous202 (always, no account enumeration)
POST/v1/auth/password/resetreset token204

POST /v1/auth/google verifies a Google ID token server-side and issues a bAInquet session with the same shape as login.

Two-factor authentication is available: GET /v1/auth/2fa (status), POST /v1/auth/2fa/setup (begin TOTP enrollment), /activate (verify a code and receive one-time recovery codes), /disable, and /recovery-codes. Login enforces the second factor when enabled (auth.mfa_required, then a TOTP or single-use recovery code).

INFO

/v1/auth/sessions and /v1/auth/org/switch are not mounted in the current build.

Example: register

http
POST /v1/auth/register
Content-Type: application/json

{ "email": "owner@acme.com", "password": "a-strong-password", "orgName": "Acme" }
json
{
  "ok": true,
  "data": { "userId": "d1f0...", "emailVerificationSent": true },
  "meta": { "requestId": "req_01J..." }
}

Organizations (/v1/organizations)

Every route requires a user; the service enforces per-org RBAC and tenant scope.

MethodPathMin role
GET/v1/organizationsviewer (member)
POST/v1/organizationsany user (caller becomes owner)
GET/v1/organizations/:idmember
PATCH/v1/organizations/:idorganization_admin
DELETE/v1/organizations/:idorganization_owner
GET/v1/organizations/:id/membersmember
POST/v1/organizations/:id/membersorganization_admin
PATCH/v1/organizations/:id/members/:userIdorganization_admin
DELETE/v1/organizations/:id/members/:userIdorganization_admin
POST/v1/organizations/:id/eraseorganization_owner
POST/v1/organizations/:id/api-keysorganization_admin
GET/v1/organizations/:id/api-keysorganization_admin
DELETE/v1/organizations/:id/api-keys/:keyIdorganization_admin
POST/v1/organizations/:id/transferorganization_owner
POST/v1/organizations/:id/transfer/cancelorganization_owner
POST/v1/organizations/transfers/:token/acceptthe target user

POST /v1/organizations/:id/erase is GDPR Article 17 hard erasure: it requires a typed confirmation matching the org name and returns 202.

API keys are created under /v1/organizations/:id/api-keys (the bnq_live_* secret is shown once). Organization ownership transfer is a pending-then-accept flow: the owner initiates to a target email, the recipient accepts with the returned token, and the reassignment runs as the org.transfer.execute job.

Websites (/v1/websites)

Fully implemented.

MethodPathMin role
GET/v1/organizations/:orgId/websitesviewer
POST/v1/organizations/:orgId/websitesorganization_admin
GET/v1/websites/:idviewer
PATCH/v1/websites/:ideditor
DELETE/v1/websites/:idorganization_admin

Website transfer and webhook management are available:

MethodPathMin role
POST/v1/websites/:id/transferorganization_admin (source) + organization_owner (target)
POST/v1/websites/:id/webhooksorganization_admin
GET/v1/websites/:id/webhooksorganization_admin
PATCH/v1/websites/:id/webhooks/:webhookIdorganization_admin
DELETE/v1/websites/:id/webhooks/:webhookIdorganization_admin

A webhook's signing secret is returned once on create and stored only as a hash.

Verification (/v1/websites/:id/verification)

Fully implemented, including the state machine and an SSRF-guarded challenge fetch.

MethodPathMin roleStatus
GET/v1/websites/:id/verificationviewer200
POST/v1/websites/:id/verification/challengeeditor201
POST/v1/websites/:id/verification/checkeditor200

challenge issues a token to place via dns_txt, html_meta/meta_tag, well_known_file, or plugin; the method drives the trust tier (see Core concepts). check re-reads ownership and advances the state machine.

Connectors (/v1/websites/:id/connectors)

Fully implemented. The created connector token is shown once at creation; only the authHash and tokenVersion persist.

MethodPathMin roleStatus
POST/v1/websites/:id/connectorsorganization_admin201 (token shown once)
GET/v1/websites/:id/connectorsuser200 (never returns secrets)
POST/v1/connectors/:cid/rotateuser200 (new token shown once)
DELETE/v1/connectors/:ciduser204

After creation the connector authenticates against the ingestion surface, not these routes.

Example: create connector

json
{
  "ok": true,
  "data": {
    "id": "9d4e2c10-...",
    "name": "wp-prod",
    "connectorType": "wordpress",
    "token": "9d4e2c10-....base64urlsecret",
    "scope": { "websiteId": "a7b2-...", "sourceTypes": ["page", "post", "product"] },
    "tokenVersion": 1
  },
  "meta": { "requestId": "req_01J..." }
}

The token is connectorId.secret, the exact value the SDK and plugins sign with. See Ingestion and signing for the scheme.

Knowledge (/v1/websites/:id/...)

Read and search over the knowledge graph, tenant scoped. Fully implemented against the Postgres graph store.

MethodPathMin roleNotes
GET/v1/websites/:id/knowledge/search?q=...viewerranked entity, fact, and chunk hits; q required
GET/v1/websites/:id/entities/:eid/factsviewerverified facts for one entity (hidden excluded)
GET/v1/websites/:id/entities/:eid/relatedviewerrelated entities via graph traversal

Search and the per-entity reads above are what the knowledge surface exposes today; the broad collection list reads (/facts, /entities, /relationships, /chunks) are not mounted in the current build. The MCP server exposes the same knowledge service for agents.

Publishing and preview (/v1/websites/:id/...)

Drives the exporter registry and the atomic, versioned publish.

MethodPathMin roleBehavior
POST/v1/websites/:id/publisheditorruns the exporter pipeline synchronously, returns 200 with { versionUlid, versionLabel, files, cdnPath }
GET/v1/websites/:id/node/previewviewerthe "what AI sees" preview
DELETE/v1/websites/:id/nodeorganization_admintakedown: deletes the latest/manifest.json pointer (node serves 404/410), keeps versions for audit; 202

Publish runs synchronously and returns 200 (a node.publish worker job also exists and is wired, but this route does the direct engine call). It does not yet pre-gate on website.not_verified. A concurrent publish of the same node holds a per-site Redis lock and returns publish.conflict. See Node files for the published file set and the versions/{ulid}/ plus latest/ layout.

DELETE node has a known bug

DELETE /v1/websites/:id/node is wired, but a known defect in the takedown path can return a server error in the current build. Treat the takedown route as not yet reliable. The published node and its version history are unaffected.

Billing and entitlements (/v1/organizations/:id/...)

Read endpoints are fully implemented; plan, capabilities, and limits are plan-derived constants and usage is read from a repo. Gated by requireOrgScope (the principal's organizationId must equal :id, or admin/super_admin bypasses).

MethodPathMin role
GET/v1/organizations/:id/entitlementsorg member (scoped)
GET/v1/organizations/:id/usageorg member (scoped)

Billing

The entitlements and usage reads above are live. Live payment processing (Stripe checkout, portal, webhooks) is wired up at launch; in the current build a checkout attempt surfaces billing.unconfigured (503).

Admin (/v1/admin)

Every route requires admin (or super_admin) and every access writes an audit_log row. Fully implemented.

MethodPathMin role
GET/v1/admin/organizationsadmin
GET/v1/admin/organizations/:id/membersadmin
GET/v1/admin/audit-logadmin
GET/v1/admin/overviewadmin
POST/v1/admin/impersonateadmin

POST /v1/admin/impersonate mints a real short-lived (900s) access JWT for the target user and writes the audit row. The minted token omits role and org; the real-admin marker lives in the audit log.

Analytics (/v1/websites/:id/analytics)

Read-only per-website rollups. Requires a user access token (any role; light tenant scope).

MethodPathMin role
GET/v1/websites/:id/analyticsviewer (member)
json
{
  "ok": true,
  "data": {
    "websiteId": "b7c8d9e0-...",
    "available": true,
    "series": [
      { "metric": "node.reads", "value": 1820, "periodStart": "2026-06-01T00:00:00Z" },
      { "metric": "facts.published", "value": 88, "periodStart": "2026-06-01T00:00:00Z" }
    ]
  },
  "meta": { "requestId": "req_01J..." }
}

Waitlist (/v1/waitlist)

Public marketing capture. No auth; abuse-controlled by the pre-auth rate limiter.

MethodPathAuthStatus
POST/v1/waitlistnone202

A valid submit always returns 202. A duplicate email returns the same 202, membership is never leaked. A malformed email or missing consent returns 422 validation.failed; abuse returns 429 quota.rate_limited.

http
POST /v1/waitlist
Content-Type: application/json

{ "email": "future-customer@acme.com", "source": "landing_hero", "consent": true }
json
{ "ok": true, "data": { "status": "accepted" }, "meta": { "requestId": "req_01J..." } }

Owner-controlled structured data for AI.