Skill: karimsherifyehia/nexus (version 1.0.5)

\--- name: nexus-agent description: Use Nexus as an AI agent through the hosted MCP server or direct Nexus API. Supports authenticated read operations by default and explicit-confirmation write operations for CRM, orders, inventory, messaging, shipping, internal chat, social publishing, invites, and conversation intelligence. homepage: https://nexus-docs.aiforstartups.io user-invocable: true disable-model-invocation: true metadata: {"openclaw":{"requires":{"env":\["NEXUS\_API\_KEY"]},"primaryEnv":"NEXUS\_API\_KEY","homepage":"https://nexus-docs.aiforstartups.io"}} --- # Nexus Agent Skill Nexus is a multi-tenant operations OS for ecommerce and retail workflows. Use this skill to authenticate an AI agent, access the hosted Nexus MCP server, and fall back to direct HTTP API access when MCP is not sufficient. This skill is intentionally **user-invocable only** and is designed to be **read-first**. Any write operation requires explicit user confirmation. ## Required credential This skill requires the environment variable: * `NEXUS\_API\_KEY` `NEXUS\_API\_KEY` must be an **agent API key**, not a human JWT and not a short-lived agent JWT. ### Credential handling rules * Read only `NEXUS\_API\_KEY` from the runtime environment. * If `NEXUS\_API\_KEY` is missing, ask the user or operator to configure it. * Do not read unrelated environment variables, local files, or stored secrets. * Do not hardcode live secrets into the skill. * Use the raw API key **only** with `agent-auth`. * After exchanging the API key for a short-lived JWT, use that JWT for MCP and direct API calls. ### Optional runtime config for direct PostgREST access When calling PostgREST directly, the runtime must also provide an anon key header such as: * `VITE\_SUPABASE\_ANON\_KEY` * `NEXT\_PUBLIC\_SUPABASE\_ANON\_KEY` * or an operator-provided equivalent runtime secret Do not publish a live anon key inside distributed skills or public documentation. ### Example OpenClaw configuration ```json { "skills": { "entries": { "nexus-agent": { "apiKey": { "source": "env", "provider": "default", "id": "NEXUS\_API\_KEY" } } } } } ``` ## Safety and execution policy ### Trusted domains only Only call Nexus on these hosts: * `api.nexus.aiforstartups.io` * `nexus-docs.aiforstartups.io` Do not send Nexus credentials or payloads to any other host. ### Read-first behavior Default to read operations whenever possible. Examples of read operations: * list contacts, orders, inventory, conversations, call logs, invites, or internal conversations * get a single contact, order, shipment, post, or annotation * read MCP schema resources and organization info * run search and analytics reads ### Write gate Require explicit user confirmation before any write action, including: * creating or updating contacts * creating orders or updating order status * sending customer messages * sending internal team messages * creating, updating, publishing, or deleting social posts * inviting, resending, or cancelling organization invites * updating message annotations, AI tag definitions, prompts, or org AI context * initiating a phone call * creating an AWB or triggering fulfillment steps If the current API key has only `read` scope, do not attempt write actions. ### No autonomous onboarding or background writes * Do not autonomously register organizations, create accounts, or start onboarding flows. * Do not autonomously create orders, publish posts, send messages, or change records in the background. * If the user explicitly asks to onboard or enable a Nexus integration, ask only for the minimum information required for that step. ## Authentication AI agents do **not** use human user JWTs directly. The correct flow is: 1. A human org admin creates an agent API key with `agent-api-key-create` 2. The agent exchanges that key for a short-lived JWT with `agent-auth` 3. The agent uses the short-lived JWT for MCP or direct API calls ### Step 1: human admin creates an API key This is a human-admin operation. ```bash POST https://api.nexus.aiforstartups.io/functions/v1/agent-api-key-create Authorization: Bearer <human-admin-jwt> Content-Type: application/json { "name": "Bahig - Karim assistant", "scopes": \["read", "write"], "expires\_at": "2026-12-31T23:59:59Z" } ``` Response: ```json { "api\_key": "nxs\_ak\_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ12", "key\_prefix": "nxs\_ak\_ab12CD34", "organization\_id": "uuid", "name": "Bahig - Karim assistant", "scopes": \["read", "write"], "expires\_at": "2026-12-31T23:59:59.000Z", "message": "Store this API key now. It is only returned once." } ``` Important: * the raw key is returned **once only** * Nexus stores only a **bcrypt hash** * keys can be revoked ### Step 2: exchange API key for an agent JWT ```bash POST https://api.nexus.aiforstartups.io/functions/v1/agent-auth Content-Type: application/json {"api\_key": "nxs\_ak\_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} ``` Response: ```json { "access\_token": "eyJ...", "organization\_id": "uuid", "expires\_in": 3600 } ``` Use the `access\_token` as `Authorization: Bearer <token>` on all subsequent requests. ### API key format `nxs\_ak\_` prefix + 48 random alphanumeric characters. ### Scopes * `read` = read-only operations * `write` = read + create + update * `admin` = full access ### MCP authentication The hosted MCP endpoint accepts the **agent JWT**, not the raw API key: ```bash POST https://api.nexus.aiforstartups.io/functions/v1/mcp-server Authorization: Bearer <agent-jwt> ``` ## Base URLs |Service|URL| |-|-| |Edge Functions|`https://api.nexus.aiforstartups.io/functions/v1/<function-name>`| |PostgREST (DB)|`https://api.nexus.aiforstartups.io/rest/v1/<table>`| |MCP|`https://api.nexus.aiforstartups.io/functions/v1/mcp-server`| For direct PostgREST calls, include both: * `Authorization: Bearer <agent-jwt>` * `[REDACTED_SECRET]` ## Preferred integration path: MCP first Prefer the hosted MCP server when possible. It exposes a stable, documented tool surface for common agent workflows. ### MCP tools * `nexus\_list\_contacts` * `nexus\_get\_contact` * `nexus\_create\_contact` * `nexus\_update\_contact` * `nexus\_list\_orders` * `nexus\_get\_order` * `nexus\_create\_order` * `nexus\_update\_order\_status` * `nexus\_list\_inventory` * `nexus\_check\_stock` * `nexus\_list\_conversations` * `nexus\_send\_message` * `nexus\_search` * `nexus\_sync\_social\_posts` * `nexus\_list\_social\_posts` * `nexus\_get\_social\_post` * `nexus\_create\_social\_post` * `nexus\_update\_social\_post` * `nexus\_publish\_social\_post` * `nexus\_delete\_social\_post` * `nexus\_list\_social\_accounts` * `nexus\_get\_social\_analytics` * `nexus\_get\_content\_calendar` * `nexus\_list\_organization\_invites` * `nexus\_invite\_organization\_member` * `nexus\_cancel\_organization\_invite` * `nexus\_resend\_organization\_invite` * `nexus\_list\_internal\_conversations` * `nexus\_get\_internal\_messages` * `nexus\_open\_internal\_dm` * `nexus\_send\_internal\_message` * `nexus\_list\_ai\_tag\_definitions` * `nexus\_list\_message\_annotations` * `nexus\_get\_message\_ai\_annotation` * `nexus\_get\_org\_ai\_annotation\_settings` * `nexus\_get\_customer\_conversation\_intelligence` * `nexus\_get\_conversation\_intelligence\_dashboard` * `nexus\_update\_message\_annotation` * `nexus\_update\_ai\_tag\_definition` * `nexus\_set\_org\_ai\_annotation\_prompt` * `nexus\_update\_org\_conversation\_intelligence\_context` ### MCP resources * `nexus://organization/info` * `nexus://schema/contacts` * `nexus://schema/orders` * `nexus://schema/inventory` * `nexus://schema/social` * `nexus://schema/conversation-intelligence` ### When to use MCP vs direct API Use MCP when: * the client supports MCP * you want a compact tool surface * you want resource discovery and schema resources * you need social or content-calendar operations through a stable agent interface Use direct API when: * the client does not support MCP * you need raw PostgREST querying * you are debugging a low-level integration * you need a workflow not yet covered by the MCP wrapper ### Social MCP notes * Social tools require at least the `starter` plan. * Social write tools require `write` or `admin` scope. * Facebook and Instagram drafts and publishing are supported from MCP. * Instagram sync reads from the connected Instagram Business account and writes normalized rows into `social\_posts`. * `twitter` and `linkedin` remain reserved in request enums for forward compatibility and are not yet writable from this MCP surface. ### Organization invites (MCP + `agent-invite`) Agents can manage `organization\_invites` for **their JWT organization only**. * **MCP tools:** `nexus\_list\_organization\_invites` (read scope), `nexus\_invite\_organization\_member`, `nexus\_cancel\_organization\_invite`, `nexus\_resend\_organization\_invite` (write scope) * **HTTP API:** `POST /functions/v1/agent-invite` with `Authorization: Bearer <agent-jwt>` and JSON body `{ "action": "invite"|"cancel"|"resend"|"list", ... }` Requires `BREVO\_KEY` on Edge Functions. ### Internal team chat (MCP) Internal chat is separate from the customer omnichannel inbox (`chats` / `messages`). MCP tools talk to `internal\_conversations` and `internal\_messages`. * the agent API key must be linked to an `agent\_accounts` row (`agent\_api\_keys.agent\_id`) * `nexus\_open\_internal\_dm`: pass `user\_id` or `user\_email` of an active org member * `nexus\_send\_internal\_message`: `sender\_id` is null server-side; humans still send with their user id in the app * `nexus\_list\_internal\_conversations` and `nexus\_get\_internal\_messages`: only threads the agent created or has posted in ## Core operations ### Contacts (CRM) > `journey\_stage\_id` is a UUID foreign key to `customer\_journey\_stages`, not a free-text field. ```bash # List contacts GET /rest/v1/customers?organization\_id=eq.<org\_id>\&order=created\_at.desc\&limit=25 Authorization: Bearer <agent-jwt> [REDACTED_SECRET] # Search by phone (URL-encode + as %2B) GET /rest/v1/customers?phone\_number=eq.%2B201234567890\&organization\_id=eq.<org\_id> # Create contact — requires explicit confirmation and write scope POST /rest/v1/customers {"first\_name":"Ahmed","last\_name":"Hassan","phone\_number":"+201234567890","organization\_id":"<org\_id>"} # Update tags — requires explicit confirmation and write scope PATCH /rest/v1/customers?id=eq.<uuid> {"tags":\["vip","repeat-buyer"]} ``` ### Messages (omnichannel inbox) > `chats` has no `status` column. Use `is\_archived`. `messages` sorts by `timestamp`, not `created\_at`. ```bash # List open conversations GET /rest/v1/chats?organization\_id=eq.<org\_id>\&is\_archived=eq.false\&order=last\_message\_time.desc # Get messages in a conversation GET /rest/v1/messages?chat\_id=eq.<chat\_id>\&order=timestamp.asc # Send WhatsApp message — requires explicit confirmation and write scope POST /functions/v1/whatsapp-send-message {"chatId":"<chat\_id>","message":"Hello from Nexus AI agent","organizationId":"<org\_id>"} # Send WhatsApp template — requires explicit confirmation and write scope POST /functions/v1/whatsapp-send-template {"phone\_number":"+201234567890","template\_name":"order\_shipped","language":"ar","components":\[]} ``` ### Orders > The status column is `order\_status`, not `status`. ```bash # List orders GET /rest/v1/orders?organization\_id=eq.<org\_id>\&order=created\_at.desc\&limit=50 # Filter by status — use order\_status GET /rest/v1/orders?organization\_id=eq.<org\_id>\&order\_status=eq.pending # Create order — requires explicit confirmation and write scope POST /rest/v1/orders {"customer\_id":"<uuid>","organization\_id":"<org\_id>","order\_status":"pending","total\_amount":450,"currency":"EGP"} # Update order status — requires explicit confirmation and write scope PATCH /rest/v1/orders?id=eq.<uuid> {"order\_status":"confirmed"} # Get order items (qty column, not quantity) GET /rest/v1/order\_items?order\_id=eq.<order\_id> ``` ### Inventory > Inventory uses `items` plus `stock\_balances`. There is no `inventory\_items` table. ```bash # List products GET /rest/v1/items?organization\_id=eq.<org\_id>\&is\_active=eq.true\&order=updated\_at.desc # Search by SKU GET /rest/v1/items?organization\_id=eq.<org\_id>\&sku=eq.TSHIRT-M-BLK # Low stock GET /rest/v1/stock\_balances?organization\_id=eq.<org\_id>\&available\_quantity=lt.10 # Stock for one item across warehouses GET /rest/v1/stock\_balances?item\_id=eq.<uuid> ``` ### Shipping ```bash # Create AWB — requires explicit confirmation and write scope POST /functions/v1/create-awb-bosta {"order\_id":"<uuid>","pickup\_date":"2025-06-17"} # List shipments GET /rest/v1/awbs?organization\_id=eq.<org\_id>\&order=created\_at.desc # Get tracking history GET /rest/v1/awb\_status\_logs?awb\_id=eq.<awb\_id>\&order=timestamp.desc ``` ### VoIP ```bash # Initiate call — requires explicit confirmation and write scope POST /functions/v1/call-initiate {"phone\_number":"+201234567890"} # Get call logs for a contact GET /rest/v1/call\_logs?customer\_id=eq.<uuid>\&order=call\_date.desc ``` ### Conversation intelligence and CS AI Prefer the MCP tools for conversation intelligence. They map to the current schema and RPCs more safely than ad hoc direct queries. Read-oriented MCP tools: * `nexus\_list\_ai\_tag\_definitions` * `nexus\_list\_message\_annotations` * `nexus\_get\_message\_ai\_annotation` * `nexus\_get\_org\_ai\_annotation\_settings` * `nexus\_get\_customer\_conversation\_intelligence` * `nexus\_get\_conversation\_intelligence\_dashboard` Write-capable MCP tools that require explicit confirmation and write scope: * `nexus\_update\_message\_annotation` * `nexus\_update\_ai\_tag\_definition` * `nexus\_set\_org\_ai\_annotation\_prompt` * `nexus\_update\_org\_conversation\_intelligence\_context` ## Pagination Use either the `Range` header or `offset` and `limit` query parameters. ```bash GET /rest/v1/customers Range: 0-24 ``` Response includes `Content-Range: 0-24/1523`. ## Filtering Common PostgREST operators: `eq`, `neq`, `gt`, `gte`, `lt`, `like`, `in`, `is` ```bash # Multiple filters — note order\_status, not status GET /rest/v1/orders?organization\_id=eq.<org\_id>\&order\_status=eq.shipped\&created\_at=gte.2025-01-01 # Null check GET /rest/v1/orders?shopify\_order\_id=is.null ``` ## Error handling |Code|Meaning|Action| |-|-|-| |401|Invalid or expired token|Re-authenticate via `agent-auth`| |403|Scope not allowed|Check API key scopes| |404|Record not found|Verify the UUID and org scope| |429|Rate limited|Retry after `Retry-After` seconds| ## Deployed endpoints for agents ### Agent bootstrap * `POST /functions/v1/agent-api-key-create` * `POST /functions/v1/agent-auth` * `POST /functions/v1/mcp-server` ### Outbound messaging used by MCP * `POST /functions/v1/whatsapp-send-message` * `POST /functions/v1/facebook-send-message` * `POST /functions/v1/instagram-send-message` ### Notes * `agent-auth` is rate limited to **10 attempts per minute per IP** * agent JWTs expire after **3600 seconds** * use the raw API key only with `agent-auth` ## Support If you encounter an error or undocumented behavior, contact: * **To:** [REDACTED_SECRET] * **CC:** [REDACTED_SECRET] Include the endpoint, the error message, and a short description of the attempted action. ## Additional resources * Full API reference: [api-reference.md](api-reference.md) * Hosted MCP docs: `https://nexus-docs.aiforstartups.io/api/ai-agents-mcp` * OpenAPI spec: `https://nexus-docs.aiforstartups.io/openapi.yaml` * Docs: `https://nexus-docs.aiforstartups.io`

path: api-reference.md
content: # Nexus Agent API Reference — Exact Schema

> This file contains the real column names from the production database.
> Always use these exact names when querying PostgREST or writing filters.

---

## Credential and safety profile for distributed agent skills

If this API reference is used inside a public or shareable agent skill:

- declare `NEXUS_API_KEY` explicitly as the required environment variable
- treat `NEXUS_API_KEY` as the primary credential
- exchange it at `agent-auth` for a short-lived JWT before calling MCP or direct API endpoints
- default to read operations
- require explicit user confirmation before any write action
- use only `api.nexus.aiforstartups.io` and `nexus-docs.aiforstartups.io`
- never hardcode or publish a live PostgREST anon key in distributed skills

When using PostgREST directly, include `[REDACTED_SECRET]` from secure runtime configuration or operator-provided secrets such as `VITE_SUPABASE_ANON_KEY` or `NEXT_PUBLIC_SUPABASE_ANON_KEY`.

---

## Authentication flow

Use this flow exactly:

1. Human admin creates an agent API key with `agent-api-key-create`
2. Agent exchanges the key for a short-lived JWT with `agent-auth`
3. Agent uses the JWT for MCP or direct API calls

### Create agent API key

```bash
POST https://api.nexus.aiforstartups.io/functions/v1/agent-api-key-create
Authorization: Bearer <human-admin-jwt>
Content-Type: application/json

{
  "name": "Bahig - Karim assistant",
  "scopes": ["read", "write"],
  "expires_at": "2026-12-31T23:59:59Z"
}
```

### Exchange API key for agent JWT

```bash
POST https://api.nexus.aiforstartups.io/functions/v1/agent-auth
Content-Type: application/json

{
  "api_key": "nxs_ak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

Response:

```json
{
  "access_token": "eyJ...",
  "organization_id": "uuid",
  "expires_in": 3600
}
```

### Hosted MCP endpoint

```bash
POST https://api.nexus.aiforstartups.io/functions/v1/mcp-server
Authorization: Bearer <agent-jwt>
```

### Scope model

| Scope | Access |
|---|---|
| `read` | Read-only |
| `write` | Read + create + update |
| `admin` | Full access |

### Important rules

- API key format is `nxs_ak_` + 48 random characters
- raw API keys are returned once only
- Nexus stores only a bcrypt hash
- `agent-auth` is rate limited to `10` attempts per minute per IP
- agent JWTs expire after `3600` seconds

---

## MCP tools and resources

### Tools

- `nexus_list_contacts`
- `nexus_get_contact`
- `nexus_create_contact`
- `nexus_update_contact`
- `nexus_list_orders`
- `nexus_get_order`
- `nexus_create_order`
- `nexus_update_order_status`
- `nexus_list_inventory`
- `nexus_check_stock`
- `nexus_list_conversations`
- `nexus_send_message`
- `nexus_search`
- `nexus_sync_social_posts`
- `nexus_list_social_posts`
- `nexus_get_social_post`
- `nexus_create_social_post`
- `nexus_update_social_post`
- `nexus_publish_social_post`
- `nexus_delete_social_post`
- `nexus_list_social_accounts`
- `nexus_get_social_analytics`
- `nexus_get_content_calendar`
- `nexus_list_organization_invites`
- `nexus_invite_organization_member`
- `nexus_cancel_organization_invite`
- `nexus_resend_organization_invite`
- `nexus_list_internal_conversations`
- `nexus_get_internal_messages`
- `nexus_open_internal_dm`
- `nexus_send_internal_message`
- `nexus_list_ai_tag_definitions`
- `nexus_list_message_annotations`
- `nexus_get_message_ai_annotation`
- `nexus_get_org_ai_annotation_settings`
- `nexus_get_customer_conversation_intelligence`
- `nexus_get_conversation_intelligence_dashboard`
- `nexus_update_message_annotation`
- `nexus_update_ai_tag_definition`
- `nexus_set_org_ai_annotation_prompt`
- `nexus_update_org_conversation_intelligence_context`

### Resources

- `nexus://organization/info`
- `nexus://schema/contacts`
- `nexus://schema/orders`
- `nexus://schema/inventory`
- `nexus://schema/social`
- `nexus://schema/conversation-intelligence`

### Conversation intelligence MCP (CS AI)

| Concept | Detail |
|---|---|
| Message row | `ai_message_annotations` — `tags[]` slugs, `tag_group` string, `intent`, Phase 2 fields |
| Taxonomy | `ai_tag_definitions` — unique `(organization_id, scope, tag)`; `category` = tag group for that slug |
| Prompts | `org_ai_prompts.prompt_key` ∈ `tag_message`, `tag_group` |
| Org context | `org_ai_profile` — summaries, `industry`, `priority_attributes`, `custom_vocabulary`, `dashboard_focus_areas`, and related fields |
| Dashboards | RPCs `rpc_ai_get_customer_intelligence`, `rpc_ai_get_conversation_dashboard` |
| Writes | `nexus_update_message_annotation`, `nexus_update_ai_tag_definition`, `nexus_set_org_ai_annotation_prompt`, `nexus_update_org_conversation_intelligence_context` require `write` scope |

### Social MCP constraints

- Social tools require at least the `starter` plan.
- Social write tools require `write` or `admin` scope.
- Current writable social support is backed by the Meta social schema: `social_accounts`, `social_posts`, `social_post_media`, `social_post_metrics_daily`.
- Facebook and Instagram drafts and publishing are supported from MCP.
- Instagram sync reads from the connected Instagram Business account and writes normalized rows into `social_posts`.
- Request schemas still expose `twitter`, `linkedin`, `instagram`, and `facebook`; Twitter and LinkedIn remain reserved for forward compatibility and are not yet writable from MCP.

### Organization invites (agent)

| Item | Detail |
|---|---|
| MCP | `nexus_list_organization_invites` (read), `nexus_invite_organization_member`, `nexus_cancel_organization_invite`, `nexus_resend_organization_invite` (write) |
| HTTP | `POST /functions/v1/agent-invite` with agent JWT; body `{ "action": "invite"\|"cancel"\|"resend"\|"list", "email"?, "role"?, "invite_id"? }` |
| Email | Requires `BREVO_KEY`; same invitation email template as human `invite` |

### Internal team chat (agent)

| Item | Detail |
|---|---|
| Tables | `internal_conversations`, `internal_messages`, `internal_conversation_participants`; agent messages use `agent_account_id`, `sender_id` null |
| Prerequisite | `agent_api_keys.agent_id` → `agent_accounts.id` |
| MCP | `nexus_open_internal_dm` (`user_id` or `user_email`), `nexus_send_internal_message`, `nexus_list_internal_conversations`, `nexus_get_internal_messages` |

---

## Table: `customers`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations — required |
| first_name | text | |
| last_name | text | |
| email | text | |
| phone_number | text | E.164, for example `+201234567890` |
| address | jsonb | `{line1, line2, city, country}` |
| shopify_customer_id | text | nullable |
| customer_label | text | free-text label |
| journey_stage_id | uuid | FK → `customer_journey_stages` |
| lead_source_id | uuid | FK → `lead_sources` |
| assigned_to | text | user id as text |
| customer_score | integer | default `0` |
| tags | text[] | array of strings |
| last_contact | timestamptz | |
| next_follow_up | timestamptz | |
| notes | text | |
| created_at | timestamptz | |
| updated_at | timestamptz | |

```bash
# List contacts
GET /rest/v1/customers?organization_id=eq.<org_id>&order=created_at.desc&limit=25

# Search by phone
GET /rest/v1/customers?phone_number=eq.%2B201234567890&organization_id=eq.<org_id>

# Create contact
POST /rest/v1/customers
{"organization_id":"<org_id>","first_name":"Ahmed","last_name":"Hassan","phone_number":"+201234567890"}
```

---

## Table: `orders`

> The status column is named `order_status`, not `status`.

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations — required |
| customer_id | uuid | FK → customers |
| shopify_order_id | text | nullable |
| order_number | text | auto-generated or set manually |
| order_status | `order_status` enum | `pending`, `confirmed`, `fulfilling`, `shipped`, `delivered`, `cancelled` |
| total_amount | numeric(10,2) | |
| currency | text | default `USD` |
| items | jsonb | embedded items array (legacy) |
| airtable_record_id | text | nullable |
| awb_number | text | nullable; filled when shipped |
| refund_amount | numeric(10,2) | default `0` |
| created_at | timestamptz | |
| updated_at | timestamptz | |

```bash
# List orders
GET /rest/v1/orders?organization_id=eq.<org_id>&order_status=eq.pending&order=created_at.desc

# Filter by multiple statuses
GET /rest/v1/orders?organization_id=eq.<org_id>&order_status=in.(pending,confirmed)

# Create order
POST /rest/v1/orders
{"organization_id":"<org_id>","customer_id":"<uuid>","order_status":"pending","total_amount":450,"currency":"EGP"}

# Update order status
PATCH /rest/v1/orders?id=eq.<uuid>
{"order_status":"confirmed"}
```

---

## Table: `order_items`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| order_id | uuid | FK → orders — required |
| item_id | uuid | FK → items (nullable) |
| sku | text | |
| name | text | product name |
| barcode | text | |
| qty | integer | required |
| unit_price | numeric(12,2) | |
| created_at | timestamptz | |

```bash
POST /rest/v1/order_items
[
  {"order_id":"<uuid>","name":"T-Shirt","sku":"TSH-M","qty":2,"unit_price":150},
  {"order_id":"<uuid>","name":"Cap","sku":"CAP-BLK","qty":1,"unit_price":100}
]
```

---

## Table: `chats` (conversations)

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| platform | text | `whatsapp`, `facebook`, `instagram` |
| platform_chat_id | text | external chat ID |
| contact_name | text | |
| contact_phone | text | |
| contact_platform_id | text | |
| last_message | text | |
| last_message_time | timestamptz | |
| unread_count | integer | |
| is_archived | boolean | default `false` |
| assigned_to | text | user id |

```bash
# Open conversations
GET /rest/v1/chats?organization_id=eq.<org_id>&is_archived=eq.false&order=last_message_time.desc

# Filter by platform
GET /rest/v1/chats?organization_id=eq.<org_id>&platform=eq.whatsapp&is_archived=eq.false
```

---

## Table: `messages`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| chat_id | uuid | FK → chats |
| content | text | message text body |
| message_type | `message_type` enum | `text`, `image`, `video`, `document`, `audio`, `template`, `reaction`, `sticker`, `location` |
| direction | text | `inbound`, `outbound` |
| status | text | `sent`, `delivered`, `read`, `failed` |
| platform_message_id | text | external ID |
| media_url | text | |
| timestamp | timestamptz | message send time |
| created_at | timestamptz | |

```bash
# Messages in a conversation
GET /rest/v1/messages?chat_id=eq.<chat_id>&order=timestamp.asc
```

---

## Table: `items` (inventory and products)

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| name | text | Common display label; some databases omit this column and use `description` and/or `item_no` instead. MCP inventory tools synthesize a `name` in responses when the DB has no `name` column. |
| description | text | |
| item_no | text | Primary SKU-like identifier for fulfillment |
| sku | text | |
| barcode | text | |
| category | text | |
| unit | text | for example `pcs`, `kg` |
| cost_price | numeric(10,2) | |
| selling_price | numeric(10,2) | |
| min_stock_level | integer | default `0` |
| is_active | boolean | default `true` |

```bash
# List active items
GET /rest/v1/items?organization_id=eq.<org_id>&is_active=eq.true&order=updated_at.desc

# Search by SKU
GET /rest/v1/items?organization_id=eq.<org_id>&sku=eq.TSH-M-BLK
```

---

## Table: `stock_balances`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| item_id | uuid | FK → items |
| warehouse_id | uuid | FK → warehouses |
| quantity | integer | total on hand |
| reserved_quantity | integer | reserved for orders |
| available_quantity | integer | computed as `quantity - reserved_quantity` |
| last_updated | timestamptz | |

```bash
# Low stock items (available < 10)
GET /rest/v1/stock_balances?organization_id=eq.<org_id>&available_quantity=lt.10

# Stock for a specific item
GET /rest/v1/stock_balances?item_id=eq.<item_id>
```

---

## Table: `awbs` (shipments)

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| awb_number | text | unique tracking number |
| order_id | uuid | FK → orders |
| courier_name | text | for example `bosta` |
| courier_tracking_number | text | |
| status | text | `created`, `picked_up`, `in_transit`, `delivered`, `returned`, `failed` |
| pickup_date | timestamptz | |
| delivery_date | timestamptz | |
| shipping_cost | numeric(10,2) | |

```bash
# List shipments
GET /rest/v1/awbs?organization_id=eq.<org_id>&order=created_at.desc

# Track by AWB number
GET /rest/v1/awbs?awb_number=eq.AWB12345678&organization_id=eq.<org_id>

# Status log
GET /rest/v1/awb_status_logs?awb_id=eq.<awb_id>&order=timestamp.desc
```

---

## Table: `call_logs`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| customer_id | uuid | FK → customers (nullable) |
| unique_id | text | PBX call ID |
| call_date | timestamptz | required |
| caller_number | text | required |
| called_number | text | required |
| extension | text | |
| direction | `call_direction` enum | `inbound`, `outbound`, `internal` |
| status | `call_status` enum | `answered`, `missed`, `busy`, `failed`, `voicemail` |
| duration_seconds | integer | |
| agent_notes | text | |

```bash
# Calls for a contact
GET /rest/v1/call_logs?customer_id=eq.<uuid>&order=call_date.desc

# Missed calls today
GET /rest/v1/call_logs?organization_id=eq.<org_id>&status=eq.missed&call_date=gte.2026-03-05
```

---

## Table: `warehouses`

| Column | Type | Notes |
|---|---|---|
| id | uuid | PK |
| organization_id | uuid | FK → organizations |
| name | text | required |
| location | text | |
| is_active | boolean | |

```bash
GET /rest/v1/warehouses?organization_id=eq.<org_id>&is_active=eq.true
```

---

## Common mistakes to avoid

| Wrong | Correct |
|---|---|
| `orders?status=eq.pending` | `orders?order_status=eq.pending` |
| `orders?status=eq.processing` | `orders?order_status=eq.fulfilling` |
| `items` for inventory stock | `stock_balances` joined with `items` |
| `messages?direction=eq.in` | `messages?direction=eq.inbound` |
| `chats?status=eq.open` | `chats?is_archived=eq.false` |

---

## Full order-to-ship workflow

```text
1. POST /rest/v1/orders          → order_status: pending
2. PATCH /rest/v1/orders?id=eq.X → {"order_status": "confirmed"}
3. POST /functions/v1/start-picking {"order_id": "<id>"}
4. POST /functions/v1/complete-pick {"pick_list_id": "<id>"}
5. PATCH /rest/v1/orders?id=eq.X → {"order_status": "fulfilling"}
6. POST /functions/v1/create-awb-bosta {"order_id": "<id>"}
7. AWB created → order_status auto-updated to "shipped"
```


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)