> ## Documentation Index
> Fetch the complete documentation index at: https://flow9.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Flow9 API guide

> https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api

## Getting Started

### Base URL

```
https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api
```

All endpoints are versioned under `/v1`.

> **Building an integration?** Start with [objects.md](/api-reference/objects). `GET /v1/objects`
> and `GET /v1/objects/{object}/schema` let you discover the available entities and
> their fields at runtime, including fields the tenant has added or disabled — so you
> do not have to hard-code a field list that a customer can invalidate. Then see
> [records.md](/api-reference/records) for reading and writing those records, and
> [webhooks.md](/api-reference/webhooks) for receiving events in real time.

### Authentication

Every request (except the public endpoints `/v1/health`, `/v1/onboarding` and
`/v1/availability`) must carry a credential — an API key in the `x-api-key` header,
or an OAuth 2.1 access token as `Authorization: Bearer <jwt>` (see [auth.md](/api-reference/authentication)):

```
x-api-key: f9_live_your_key_here
```

Keys are `f9_live_…` (real data) or `f9_test_…` (sandbox — see [test-mode.md](/api-reference/test-mode)).
API keys are created in **Settings > API Keys** within the CRM dashboard, or headlessly
via `POST /v1/api-keys`. Each key has:

* **Scopes** that control which endpoints it can access
* **Rate limits** (per minute and per hour — see [rate-limits.md](/api-reference/rate-limits))
* **Optional IP allowlist** (supports CIDR notation)
* **Optional expiration date**

### Request Format

* All request bodies must be valid JSON with `Content-Type: application/json`
* Maximum request body size: **100 KB**
* Maximum JSON nesting depth: **5 levels**
* Maximum keys per JSON object: **100**

### Response Format

All responses use a consistent envelope:

**Success:**

```json theme={null}
{
  "success": true,
  "data": { ... },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": "2026-02-15T10:00:00.000Z",
    "rate_limit": {
      "limit": 60,
      "remaining": 58,
      "reset": 1718400060000
    }
  }
}
```

**Error:**

```json theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": {
      "fields": {
        "first_name": "First name is required"
      }
    }
  },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": "2026-02-15T10:00:00.000Z"
  }
}
```

### Response Headers

Every response includes:

| Header | Description |
| - | - |
| `X-Request-Id` | Unique request identifier for support inquiries |
| `X-RateLimit-Limit` | Maximum requests per minute for this key |
| `X-RateLimit-Remaining` | Remaining requests in the current window |
| `X-RateLimit-Reset` | Unix timestamp (ms) when the window resets |
| `X-Content-Type-Options` | `nosniff` |
| `X-Frame-Options` | `DENY` |
| `Strict-Transport-Security` | HSTS header |

On `429` responses, `Retry-After` is also included (seconds to wait).

***

## Rate Limiting

Rate limits are enforced per API key at two windows:

| Window | Default | Scope |
| - | - | - |
| Per minute | 60 | Per endpoint |
| Per hour | 1,000 | Global per key |

Both windows use a sliding window algorithm. If either is exceeded, you'll receive a `429` response.

### Handling 429 Responses

```javascript theme={null}
const response = await fetch(url, options);
if (response.status === 429) {
  const retryAfter = parseInt(response.headers.get('Retry-After') || '5');
  await new Promise(r => setTimeout(r, retryAfter * 1000));
  // Retry the request
}
```

### Best Practices

1. Respect `Retry-After` headers — don't retry immediately
2. Monitor `X-RateLimit-Remaining` to pace your requests
3. Use exponential backoff for retries
4. Batch operations where possible instead of individual calls

***

## Endpoints

### Health Check

Check if the API is operational. No authentication required.

```
GET /v1/health
```

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "status": "ok",
    "version": "1.0.0",
    "timestamp": "2026-02-15T10:00:00.000Z"
  }
}
```

***

### Onboard Company

Create a new company with an admin user and default configuration.

```
POST /v1/onboard
```

**Required scope:** `onboarding`

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `company.name` | string | Yes | Company name (2-200 chars) |
| `company.email` | string | Yes | Company email |
| `company.phone` | string | No | Phone number |
| `company.website` | string | No | Website URL |
| `company.address` | string | No | Street address |
| `company.city` | string | No | City |
| `company.state` | string | No | State/province |
| `company.country` | string | No | Country |
| `company.postal_code` | string | No | Postal code |
| `admin_user.email` | string | Yes | Admin email |
| `admin_user.full_name` | string | Yes | Admin name (2+ chars) |
| `admin_user.password` | string | No | Password (8+ chars). Auto-generated if omitted. |
| `region` | string | Yes | `middle_east`, `europe`, or `america` |
| `industry` | string | Yes | Industry name (2-100 chars, e.g. `travel`, `real-estate`, `hospitality`) |
| `send_welcome_email` | boolean | No | Send welcome email (default: `true`) |
| `metadata` | object | No | Arbitrary metadata stored on user record |

**Example:**

```bash theme={null}
curl -X POST \
  'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/onboard' \
  -H 'x-api-key: f9_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "company": {
      "name": "Acme Travel",
      "email": "info@acmetravel.com",
      "city": "Dubai",
      "country": "UAE"
    },
    "admin_user": {
      "email": "admin@acmetravel.com",
      "full_name": "John Smith"
    },
    "region": "middle_east",
    "industry": "travel"
  }'
```

**What gets created:**

* Company record with `active` status
* Default branch ("Main Branch")
* Auth user (with auto-generated password if not provided)
* User record linked to company and branch
* Company\_Admin role assignment
* 6 default deal statuses (New, Contacted, Qualified, Proposal, Won, Lost)
* 5 default lead statuses (New, Contacted, Qualified, Converted, Disqualified)
* 10 default activity types (Call, Email, Meeting, Note, Task, LinkedIn, WhatsApp, SMS, Postal Mail, Other)
* Welcome email (unless `send_welcome_email: false`)
* Audit log entry

**Rollback:** If user creation fails, the company is automatically deleted.

***

### Get Customer by ID

Retrieve a customer profile with optional related data.

```
GET /v1/customers/:id?include=contacts,deals_count
```

**Required scope:** `customers:read`

**Path parameters:**

| Parameter | Type | Description |
| - | - | - |
| `id` | UUID | Customer ID |

**Query parameters:**

| Parameter | Type | Default | Description |
| - | - | - | - |
| `include` | string | `contacts` | Comma-separated: `contacts`, `deals_count`, `leads_count`, `activities_count`, `custom_fields` |

**Example:**

```bash theme={null}
curl 'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/customers/550e8400-e29b-41d4-a716-446655440000?include=contacts,deals_count,custom_fields' \
  -H 'x-api-key: f9_live_your_key_here'
```

***

### Lookup Customer by Email

Find a customer by their email address.

```
GET /v1/customers?email=sarah@example.com
```

**Required scope:** `customers:read`

**Query parameters:**

| Parameter | Type | Required | Description |
| - | - | - | - |
| `email` | string | Yes | Customer email to search |

**Example:**

```bash theme={null}
curl 'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/customers?email=sarah@example.com' \
  -H 'x-api-key: f9_live_your_key_here'
```

***

### Create Lead

Create a new lead with optional assignment, tags, and custom fields.

```
POST /v1/leads
```

**Required scope:** `leads:write`

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `first_name` | string | Yes | First name (1-100 chars) |
| `last_name` | string | No | Last name |
| `email` | string | Conditional | At least one of `email` or `mobile` required |
| `mobile` | string | Conditional | At least one of `email` or `mobile` required |
| `inquiry_details` | string | No | Inquiry details (max 5000 chars) |
| `notes` | string | No | Notes (max 5000 chars) |
| `source` | string | No | Lead source name (resolved against configured sources) |
| `priority` | integer | No | 1 (highest) to 4 (lowest), default: 3 |
| `origin` | string | No | Lead origin |
| `assigned_to_email` | string | No | Email of assignee (must be active company user) |
| `lead_status` | string | No | Status name (resolved against configured statuses, defaults to first) |
| `custom_fields` | object | No | Key-value custom fields |
| `tags` | string\[] | No | Array of tag names |
| `metadata` | object | No | Arbitrary metadata (stored in audit log) |

**Example:**

```bash theme={null}
curl -X POST \
  'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/leads' \
  -H 'x-api-key: f9_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "first_name": "Sarah",
    "last_name": "Connor",
    "email": "sarah@example.com",
    "mobile": "+971501234567",
    "inquiry_details": "Interested in Maldives package",
    "source": "website",
    "priority": 2,
    "lead_status": "New",
    "tags": ["vip", "corporate"]
  }'
```

**Notes:**

* If the company has duplicate detection enabled, a `409` is returned when a lead with the same email exists
* The `lead_status` and `source` fields are resolved by name (case-insensitive) against the company's configured values
* If `assigned_to_email` is omitted, the lead is assigned to the first active company user

***

### Create Activity

Log a new activity against a deal or lead.

```
POST /v1/activities
```

**Required scope:** `activities:write`

**Request body:**

| Field | Type | Required | Description |
| - | - | - | - |
| `entity_type` | string | Yes | `deal` or `lead` |
| `entity_id` | UUID | Yes | ID of the deal or lead |
| `activity_type` | string | Yes | Must match configured type (e.g. `call`, `email`, `meeting`) |
| `summary` | string | Yes | Activity summary (1-500 chars) |
| `details` | string | No | Extended details (max 10,000 chars) |
| `performed_by_email` | string | No | Performer's email (defaults to first active user) |
| `performed_at` | ISO 8601 | No | When it occurred (defaults to now, cannot be future) |
| `followup_at` | ISO 8601 | No | Scheduled follow-up (must be in the future) |
| `metadata` | object | No | Arbitrary metadata |

**Example:**

```bash theme={null}
curl -X POST \
  'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/activities' \
  -H 'x-api-key: f9_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "entity_type": "deal",
    "entity_id": "550e8400-e29b-41d4-a716-446655440000",
    "activity_type": "call",
    "summary": "Discussed pricing for Maldives package",
    "details": "Client interested in 5-night stay at Soneva Fushi",
    "followup_at": "2026-02-20T14:00:00.000Z"
  }'
```

**Side effects:**

* Updates the entity's `last_contacted_at`
* Updates `next_followup_at` if `followup_at` is provided
* Triggers SLA tracking update
* Creates audit log entry

***

### Update Activity

Update an existing activity.

```
PATCH /v1/activities/:id?entity_type=deal
```

**Required scope:** `activities:update`

**Path parameters:**

| Parameter | Type | Description |
| - | - | - |
| `id` | UUID | Activity ID |

**Query parameters:**

| Parameter | Type | Required | Description |
| - | - | - | - |
| `entity_type` | string | Yes\* | `deal` or `lead` (\*can also be in body) |

**Updatable fields:** `summary`, `details`, `activity_type`, `followup_at`, `performed_at`

At least one field must be provided.

**Example:**

```bash theme={null}
curl -X PATCH \
  'https://mwxpyoqrtdfxubdotbmj.supabase.co/functions/v1/public-api/v1/activities/550e8400-e29b-41d4-a716-446655440000?entity_type=deal' \
  -H 'x-api-key: f9_live_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "summary": "Updated: client confirmed interest",
    "followup_at": "2026-02-22T10:00:00.000Z"
  }'
```

**Notes:**

* Set `followup_at` to `null` to clear the scheduled follow-up
* The activity must belong to an entity within your company

***

## Error Code Reference

| Code | HTTP Status | Description |
| - | :-: | - |
| `MISSING_API_KEY` | 401 | No `x-api-key` header provided |
| `AUTH_INVALID_KEY` | 401 | API key not found or invalid format |
| `API_KEY_EXPIRED` | 403 | API key has passed its expiration date |
| `API_KEY_REVOKED` | 403 | API key has been revoked |
| `FORBIDDEN_SCOPE` | 403 | API key doesn't have the required scope |
| `IP_NOT_ALLOWED` | 403 | Client IP not in key's allowlist |
| `RATE_LIMIT_EXCEEDED` | 429 | Too many requests |
| `VALIDATION_ERROR` | 400 | Request body validation failed |
| `MISSING_REQUIRED_FIELD` | 400 | A required field is missing |
| `INVALID_FIELD_FORMAT` | 400 | A field has an invalid format |
| `VALIDATION_ERROR` | 400 | Request body is not valid JSON |
| `NOT_FOUND` | 404 | Requested resource not found |
| `ALREADY_EXISTS` | 409 | Resource already exists (duplicate) |
| `INTERNAL_ERROR` | 500 | Unexpected server error |

***

## Available Scopes

| Scope | Endpoints |
| - | - |
| `onboarding` | `POST /v1/onboard` |
| `customers:read` | `GET /v1/customers/:id`, `GET /v1/customers?email=` |
| `leads:write` | `POST /v1/leads` |
| `activities:write` | `POST /v1/activities` |
| `activities:update` | `PATCH /v1/activities/:id` |
| `*` | All endpoints (wildcard) |

Partial wildcards are also supported: `leads:*` matches `leads:write` and `leads:read`.

***

## Changelog

### v1.0.0 (2026-02-15)

* Initial release
* Endpoints: health, onboard, customers (by ID / by email), leads create, activities create/update
* API key authentication with SHA-256 hashing
* Multi-window rate limiting (per minute + per hour)
* Input sanitization, security headers, IP allowlisting
* Audit logging and abuse detection


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.