# DJC System Developer API — Agent Guide

> Written for an AI agent. Read it once top to bottom, then work from §2 (playbook) and §5 (resources).
> Human-readable pages: https://docs.djcsystem.com/api/ · Focused guide for chatbot sequences: https://docs.djcsystem.com/api/agents/ai-flows.md

The Developer API gives you create / read / update / delete access to the records behind a DJC System workspace — leads, WhatsApp chatrooms, AI flows, knowledge, workflows, appointments, campaigns, scheduled messages and forms — using a personal token the user created on **AI and Automation → API Tokens**.

It is a *data* API. Sending a WhatsApp message, starting a broadcast or running a workflow are actions the user performs in the app; if the user asks for one of those, tell them so instead of improvising.

---

## 0. Quick start

```text
1. GET  /me            → confirm the token; note `resources` (read | write | null per resource)
2. GET  /resources     → the catalog: fields, required flags, filters, sorts, children, notes — trust it over memory
3. Read before you write: list/filter to find existing records
4. Write: POST (201) · PATCH partial (200) · DELETE (200)
5. Report every change by record id
```

## 1. Credentials

The user's **Copy JSON** block looks like:

```json
{ "apiToken": "djc_…", "baseUrl": "https://api2.djcsystem.com/v1", "scopes": ["leads:write", "ai-flows:write"], "firstCall": "GET /me", "catalog": "GET /resources" }
```

| Value | Use |
|---|---|
| `apiToken` | Every request: `Authorization: Bearer djc_…` (or header `X-API-Key: djc_…`). |
| `baseUrl` | All paths below are relative to it. |
| `scopes` | `<resource>:read` or `<resource>:write` (write includes read); `*:read` / `*:write` cover every resource. A call outside the scopes → `403 INSUFFICIENT_SCOPE` with `details.required` — tell the user which box to tick on the token page. |

The token acts **as the user, inside the user's workspace**. You cannot see or touch other workspaces; records outside it look like `404`. If the token text is a placeholder (`djc_…`, "<paste token>"), stop and ask the user to create one.

Sanity check:

```bash
BASE=https://api2.djcsystem.com/v1; H='Authorization: Bearer djc_…'
curl -s -H "$H" $BASE/me
# {"success":true,"data":{"email":"…","tokenKind":"token","scopes":["leads:write"],"resources":{"leads":"write","chatrooms":null,…},"catalog":"/v1/resources"}}
```

## 2. Playbook

1. **`/me` then `/resources`.** Plan from the catalog: it lists `fields` (with `required`), `updatableFields`, `filterableFields`, `sortableFields`, `children` (with their own operations and fields) and `notes` for every resource. Do not guess field names.
2. **Inspect before changing.** `GET /{resource}?limit=25` and a targeted `filter[...]` first. Summarise what exists when the user's request is open-ended.
3. **Avoid duplicates.** Leads: `filter[phone]` (digits only, country code first). Knowledge: `filter[flowId]`. Blocks: `filter[orderId]=0` to find the entry point.
4. **Send only what the catalog allows.** Unknown fields and server-owned fields (ownership, timestamps, ids) are rejected with `400`; the response's `details.fieldErrors` says which.
5. **Partial updates.** `PATCH` with just the fields to change.
6. **Delete only on explicit instruction**, and never bulk-delete without listing what will be removed and getting confirmation.
7. **Paginate properly.** Follow `nextCursor` until it is `null`; never assume one page is everything.
8. **Errors.** `400/403/404/409` — fix the request, do not retry. `401` — the token is bad; stop. `429` — wait `Retry-After` seconds. `500` — retry once.
9. **Report** ids, names and what changed. The user sees the same records in the app immediately.

## 3. Conventions

| Topic | Rule |
|---|---|
| Success | `{ "success": true, "data": … }`; lists add `nextCursor` (string or null) and `limit`. |
| Error | `{ "success": false, "error": "<human>", "code": "<CODE>", "details": {…}? }` — branch on `code` + status. |
| List query | `limit` 1–100 (default 25) · `cursor` · `sort=field:asc|desc` (field ∈ `sortableFields`) · `filter[field]=value` (≤5, equality, field ∈ `filterableFields`; `true/false/null/numbers` are typed). |
| Dates | ISO-8601 strings in UTC both ways. |
| Ids | Opaque strings; every record carries `id`. |
| Children | `GET /{resource}/{id}/{child}` lists; where the catalog lists `create/update/delete` on a child, `POST /{resource}/{id}/{child}` and `GET/PATCH/DELETE /{resource}/{id}/{child}/{childId}` work too. |
| Rate | 300 requests/min per token. |

Error codes: `BAD_REQUEST` (400) · `UNAUTHORIZED` (401) · `INSUFFICIENT_SCOPE` (403, `details.required`) · `FORBIDDEN` (403, operation not offered) · `NOT_FOUND` (404) · `CONFLICT` (409, `details` names the existing record) · `RATE_LIMITED` (429) · `INTERNAL` (500).

## 4. Routes

| Method | Path | Scope |
|---|---|---|
| GET | `/me` | any |
| GET | `/resources` | any |
| GET | `/{resource}` · `/{resource}/{id}` · `/{resource}/{id}/{child}` · `/{resource}/{id}/{child}/{childId}` | `{resource}:read` |
| POST | `/{resource}` · `/{resource}/{id}/{child}` | `{resource}:write` |
| PATCH | `/{resource}/{id}` · `/{resource}/{id}/{child}/{childId}` | `{resource}:write` |
| DELETE | `/{resource}/{id}` · `/{resource}/{id}/{child}/{childId}` | `{resource}:write` |

## 5. Resources

Field lists below are a summary; the catalog is authoritative and always current.

### `leads` — the CRM record
Create requires `name`. Common fields: `phone` (digits, country code first), `email`, `source`, `status`, `remarks`, `tags[]`, `labels[]`, `customData{}`, `converted`, `assignedTo1…5` (team emails), `whatsappFlowId`, `campaignName`, `birthday`.
Filter: `status`, `source`, `phone`, `email`, `converted`, `whatsappFlowId`, `assignedTo1`, `campaignName`. Sort: `created` (default desc).
Notes: look up by phone before creating. `tags` are plain strings; `labels` are the coloured chips.

### `chatrooms` — WhatsApp inbox conversations (list, get, update only)
Update: `status` (`open|closed|snoozed`), `name`, `unread`, `currentIntentionId`. Filter: `status`, `from`, `remotePhone`, `isGroup`, `currentIntentionId`. Sort: `date` (default desc).
Child `messages` (read-only, newest first). Rooms are created by incoming traffic; sending is not available here.

### `ai-flows` — WhatsApp chatbots
Create requires `name`. Fields: `status`, `keywords[]`, `system` (persona prompt), `flowDesign` (knowledge text), `disableAI`, `collectDataOneAtATime`, `dataFields[]`, `roundRobin`, `autoAssignmentIds[]`, `forwardNumbers`, `forwardMessage`, `pinned`, `whoCanEdit`.
Children: `blocks` (full CRUD) and `relations` (create/delete) — the follow-up sequence. **Read the AI flows guide (§6 summary below, full version at /api/agents/ai-flows.md) before touching them.**

### `knowledge-records` — text the chatbot retrieves from
Create requires `flowId`, `catalogId`, `input` (≤20 000 chars); optional `link`. Filter: `flowId`, `catalogId`. One fact per record; changing `input` re-indexes automatically.

### `catalogs` — groupings of knowledge records inside a flow
Create requires `flowId`, `name`; optional `description`. Filter: `flowId`.

### `workflows` — node-based automations
Create requires `name`; `status` `active|inactive`. Children `nodes`, `edges`, `executions` are read-only (the graph editor owns them).

### `appointments`
Create requires `custName`, `custContact`, `startDate`, `endDate` (ISO with offset), `timezone` (IANA, e.g. `Asia/Kuala_Lumpur`). Optional: `name`, `custEmail`, `description`, `locationType` (`in_person|phone|video`), `location`, `duration`, `eventTypeId`, `notes`, reminder flags. Filter: `eventTypeId`, `custContact`, `custEmail`. Sort: `startDate` (default asc), `createdDate`, `date`.

### `event-types` — bookable appointment templates
Create requires `name`, `duration` (minutes). Optional: `description`, `isActive`, `color`, `bufferTime`, `locationType`, `price`, `capacity`, `timezone`, `workingHours[]`. Filter: `isActive`.

### `blast-campaigns` — WhatsApp broadcasts
Create requires `name`. Fields: `description`, `status` (`new|in progress|paused|completed`), `message`, `messageList[]`, `timeZone`, `frequency`, `offHours[]`. Child `contacts` read-only. Create the campaign; the user picks recipients and starts it in the app.

### `scheduled-tasks` — one-off scheduled WhatsApp messages
Create requires `to` (phone), `message`, `scheduledTime` (ISO with offset); optional `type`. Update: `message`, `scheduledTime`, `status` (`scheduled|cancelled`). Filter: `status`, `to`, `type`, `campaignId`, `appointmentId`. Sort: `scheduledTime` (default asc).

### `forms` — lead-capture forms
Create requires `name`; optional `description`, `fields[{name,label,type,required,options}]`. Child `submissions` read-only.

## 6. AI-flow sequences in brief

- Blocks: `POST /ai-flows/{id}/blocks { name, blockType, position:{x,y}, orderId?, message?, … }`. Exactly one block has `orderId: 0` (the start); others default to 999. `blockType` ∈ `sequential | textNode | questionNode | assignLeadNode | labelNode | stopFlowNode | telegramNotificationNode | whatsappNotificationNode` and cannot be changed later.
- Relations: `POST /ai-flows/{id}/relations { sourceNodeId, targetNodeId, sourceHandleId? }`. The handle must exist on the **source** type: `sequential`/notification → `SEQUENTIAL`; `questionNode` → `SEQUENTIAL` (data collected) | `NO_REPLY` | `IRRELEVANT`; `textNode` → `SEQUENCE` (immediately) | `REPLIED` (default) | `NO_REPLY`; `assignLeadNode`/`labelNode`/`stopFlowNode` have no outputs. Wrong handle → `400` with `details.allowedHandles`.
- Layout: `x = 80 + n × 380` for a row; branches at `y + 320`.
- Deleting a block cascades to its relations. Relations cannot be updated — delete and recreate.

## 7. Don't

- Invent fields or ids; use the catalog and the records you read.
- Create duplicates of records that already exist.
- Delete or overwrite anything the user did not ask about.
- Claim to have sent messages, started broadcasts or run workflows — the API does not do that.
- Paste the token anywhere in your output.
