Skip to content

Connector tokens

A connector token authenticates content pushes for one website. It is scope-limited to ingestion, shown once at creation, and signs every request with an HMAC scheme. This page covers issuing, listing, rotating, and revoking tokens.

What a connector token is

  • Bound to one website. A token can ingest only into the website it was issued for; a body website_id that does not match is 403 auth.scope_violation.
  • Ingest-only scope. A connector is not part of the RBAC ladder. It cannot publish, read the dashboard, or call any non-ingestion route. Its scope additionally lists the source types it may send.
  • Shown once. The full token is returned once, at creation. Only an Argon2id authHash and a tokenVersion persist; the raw token cannot be recovered from a database dump.

The token format

A connector token is two parts joined by the first dot:

<connectorId>.<secret>

The connectorId is the connector row id. The secret is base64url and contains no dot, so the token is split on the first dot. The secret is the input material for the HMAC signing key (HKDF-SHA256, salt = websiteId, info = "bq.connector.hmac.v1"). The at-rest authHash is not the signing key, so the verifier and the signer use independent derivations of the secret. See Ingestion and signing.

The endpoints

apps/api/src/modules/connector/router.ts. The created token is shown once.

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

Errors: validation.failed, website.not_found, auth.forbidden.

Issue a token

bash
curl -sS -X POST https://api.bainquet.online/v1/websites/$WEBSITE_ID/connectors \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{ "name": "wp-prod", "connectorType": "wordpress",
        "sourceTypes": ["page","post","product"] }'

Response 201, token shown once:

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..." } }

Store the token now

data.token is the only time you will see the full token. Store it in your connector's secret store immediately. If you lose it, rotate to get a new one; you cannot retrieve the old one.

List tokens

Listing never returns secrets, only metadata (id, name, type, scope, tokenVersion, status).

bash
curl -sS https://api.bainquet.online/v1/websites/$WEBSITE_ID/connectors \
  -H "Authorization: Bearer $ACCESS_JWT"

Rotate a token

Rotation issues a new token and bumps tokenVersion. Use it on a schedule, or immediately if a token may be compromised.

bash
curl -sS -X POST https://api.bainquet.online/v1/connectors/$CONNECTOR_ID/rotate \
  -H "Authorization: Bearer $ACCESS_JWT" \
  -H 'Content-Type: application/json' \
  -d '{}'

The response carries a new token (shown once) and the new tokenVersion.

Rotation overlap

Deploy the rotated token to your connector before the old one stops working. A request signed with a stale tokenVersion is rejected 401 auth.token_revoked. The recommended sequence:

  1. Rotate, capturing the new token.
  2. Update the connector's secret store with the new token.
  3. Confirm the connector is signing with the new token (a successful ingest or heartbeat).

Until the connector cuts over, in-flight requests with the old token version will be rejected once the new version is active, so cut over promptly.

Revoke a token

bash
curl -sS -X DELETE https://api.bainquet.online/v1/connectors/$CONNECTOR_ID \
  -H "Authorization: Bearer $ACCESS_JWT"

Returns 204. A revoked or suspended connector is rejected at ingestion (401 auth.token_revoked).

How the token is used at ingestion

After creation, the connector authenticates against the ingestion surface, not these CRUD routes. Each ingest request sends Authorization: Bearer <connectorId>.<secret> plus the HMAC headers (X-Signature, X-Timestamp, X-Nonce, X-Body-Sha256) and a required Idempotency-Key. The server verifies the signature, the timestamp window, the nonce (replay guard), the secret (Argon2id at rest with a decoy verify for unknown connectors), the token version, and scope. See Ingestion and signing for the full scheme.

Owner-controlled structured data for AI.