Skip to content

API guide

The narrative companion to the live OpenAPI spec at http://localhost:3000/docs (Swagger UI).

If you want the exact request / response shape of any single endpoint, open the Swagger UI — it's generated from the Zod schemas in @jobab/shared and never goes stale. This document covers what Swagger can't: the auth model, the standard error envelope, the golden-path flow, and how to integrate Meta webhooks.

Table of contents

Your first API call in 60 seconds

bash
# 1. Seeded credentials (after `pnpm db:seed`).
EMAIL="owner@rongdhonu.dev"
PASSWORD="jobab-dev-12345"

# 2. Log in. The server sets two cookies into cookies.txt:
#      - `session`    : signed user-session token
#      - `jobab_org`  : your active org (we pick the first one for you)
curl -sS -c cookies.txt -X POST http://localhost:3000/auth/login \
  -H 'content-type: application/json' \
  -d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}"

# 3. Every call afterwards just needs `-b cookies.txt`.
curl -sS -b cookies.txt http://localhost:3000/auth/me

# 4. Read the inbox.
curl -sS -b cookies.txt http://localhost:3000/conversations | jq '.[0:2]'

# 5. Open the live Swagger and click around:
open http://localhost:3000/docs

That's the whole auth model — cookies.txt is your session.

How auth works

ConceptDetail
Session cookiesession — HttpOnly, SameSite=Lax. Set by POST /auth/login (or /sign-up, /accept-invite). Browsers persist automatically; with curl use -c cookies.txt -b cookies.txt.
Active orgjobab_org cookie — the org you're currently working with. Set on login. Switch with POST /auth/active-org if you belong to several.
Public routes/auth/login, /auth/sign-up, /auth/accept-invite, /auth/invites/inspect, /webhooks/*, /healthz, /readyz. Everything else returns 401 without a valid session cookie.
Role gatingA few endpoints require owner / admin (creating invites, removing members, updating comment rules). These return 403 if your membership role is too low.
Org isolationEvery protected query is scoped to the active org server-side. You cannot read or write into another tenant's data even if you guess their IDs — you'll get 404 (intentionally vague).

The standard error envelope

Every 4xx / 5xx response from the API uses the same shape — see the ApiError schema in Swagger:

json
{
  "statusCode": 422,
  "message": ["body.text: String must contain at most 4000 character(s)"],
  "error": "Unprocessable Entity"
}
CodeMeaningWhat you usually did wrong
400Bad requestMalformed JSON, or a business-rule violation (e.g. "email already registered").
401UnauthorisedMissing / expired session cookie. Re-login.
403ForbiddenLogged in but not allowed: wrong org, wrong role, or a public-route signature mismatch (Meta webhooks).
404Not foundResource doesn't exist, or it does but isn't visible to your org.
422Unprocessable entityRequest body failed Zod validation. message is a list of path: reason strings — show them to the user.
429Too many requestsYou hit the rate limiter (@nestjs/throttler). Backoff and retry.
500Internal server errorWe logged it (Sentry / pino). Safe to retry once; if it persists, open an issue with the x-request-id.

The golden-path flow

Read inbox → take over from the AI → reply → hand back → mark an order paid.

bash
# Take over from the AI on a specific conversation:
CONV=cm0conv123
curl -sS -b cookies.txt -X POST http://localhost:3000/conversations/$CONV/takeover

# Send a merchant reply:
curl -sS -b cookies.txt -X POST http://localhost:3000/conversations/$CONV/reply \
  -H 'content-type: application/json' \
  -d '{"text":"আপনার জন্য পেয়েছি ভাবি, একটু পরে details পাঠাচ্ছি।"}'

# Read the AI's activity for that conversation (tools, tokens, cost):
curl -sS -b cookies.txt "http://localhost:3000/conversations/$CONV/activity?limit=10" | jq

# Hand back to the AI:
curl -sS -b cookies.txt -X POST http://localhost:3000/conversations/$CONV/hand-back

# Mark an order paid manually (e.g. cash on delivery):
ORDER=cm0order123
curl -sS -b cookies.txt -X POST http://localhost:3000/orders/$ORDER/mark-paid

Endpoint catalog

Every endpoint is grouped by tag in Swagger. The table below is for at-a-glance navigation; for parameters, request / response shapes, and live examples open the corresponding tag in /docs.

TagEndpoints
authPOST /auth/login · POST /auth/sign-up · POST /auth/logout · POST /auth/accept-invite · POST /auth/active-org · GET /auth/me · GET /auth/invites/inspect
conversationsGET /conversations · GET /conversations/:id · GET /conversations/:id/messages/older?before&limit · GET /conversations/:id/activity?limit · POST /conversations/:id/takeover · POST /conversations/:id/hand-back · POST /conversations/:id/reply · POST /conversations/:id/assert-product · POST/DELETE /conversations/:id/tags[/:tagId] · GET/POST /conversations/:id/notes · DELETE /conversations/:id/notes/:noteId
tagsGET /tags · POST /tags · PATCH /tags/:id · DELETE /tags/:id
ordersGET /orders?conversationId&status&payment · GET /orders/:id · PATCH /orders/:id/status · POST /orders/:id/mark-paid
catalogGET /catalog/products?q · GET /catalog/products/:id · POST /catalog/sync/csv · POST /catalog/sync/shopify · POST /catalog/sync/woocommerce · PATCH /catalog/variants/:id/stock
teamGET /team/members · GET /team/invites · POST /team/invites · DELETE /team/invites/:id · DELETE /team/members/:id · PATCH /team/assign
commentsGET /comments?intent&postId · GET /comments/rules · PATCH /comments/rules/:intent
settingsGET /settings · PATCH /settings
analyticsGET /analytics/summary?days
onboardingGET /onboarding/status · POST /onboarding/pages
pushPOST /push/tokens · DELETE /push/tokens
webhooksGET /webhooks/meta (verify handshake) · POST /webhooks/meta (signature-verified inbound) · POST /webhooks/meta/data-deletion (Meta App Review) · POST /webhooks/meta/fake (dev DM) · POST /webhooks/meta/fake-comment (dev comment)
healthGET /healthz (liveness) · GET /readyz (DB + migrations check)

Receiving Meta webhooks

Customer DMs and post comments arrive here:

POST /webhooks/meta
x-hub-signature-256: sha256=<HMAC-SHA256(rawBody, META_APP_SECRET)>
content-type: application/json

<Meta-shaped payload>

We verify the signature byte-for-byte against META_APP_SECRET. If it doesn't match we return 403 without parsing the body. On match we acknowledge in < 50 ms (Meta retries on timeout) and the heavy work is queued onto BullMQ — the agent worker (start:worker:dev) drains it out-of-band.

The first time you subscribe, Meta calls GET /webhooks/meta?hub.mode=subscribe&hub.verify_token=<your secret>&hub.challenge=<random>. If verify_token matches META_VERIFY_TOKEN we echo back the challenge — that's the entire handshake.

For local development you can skip Meta entirely:

bash
# Inject a fake DM through the full agent loop:
curl -sS -X POST http://localhost:3000/webhooks/meta/fake \
  -H 'content-type: application/json' \
  -d '{"pageId":"page_rongdhonu","customerId":"fb_tahmina","text":"lal jamdani shari ache?"}'

MIT licensed