# 1app Grow — REST API v1

Use this API to send transactional messages and manage marketing data from external applications such as **1app**.

**Production base URL:** `https://grow.1app.online/api/v1/`

**In-app guide:** Sidebar **Developers** (`/developers`) or **Settings → Developer API**

**Full API reference (public):** `/developers/docs` — styled HTML; raw markdown at `/developers/docs/raw`

**Postman:** [Collection](/developers/docs/postman) · [Environment](/developers/docs/postman/environment) — import into Postman, set `apiToken`, and run requests.

---

## Delivery behaviour

Transactional API sends are **immediate** — they do not enter the campaign queue.

| Path | Behaviour |
|------|-----------|
| `POST /messages/*` | Synchronous HTTP: provider is called in the same request; response includes `sent` or `failed`. |
| Campaign broadcasts | Queued separately with rate limiting — transactional sends are not delayed by active campaigns. |

OTP and other transactional traffic from **1app** will not wait behind an active WhatsApp/SMS/email campaign. For best isolation during large broadcasts, use a dedicated WhatsApp number for authentication templates.

---

## Postman

1. Download the [Postman collection](/developers/docs/postman) (optional [environment](/developers/docs/postman/environment)).
2. In Postman: **Import** → upload the JSON file, or **Import → Link** and paste the collection URL.
3. Set collection variable `apiToken` to your API token (`read` + `write`).
4. Confirm `baseUrl` matches your deployment (defaults to this server's `/api/v1` URL on download).

The collection includes transactional sends (WhatsApp/SMS OTP, email, SMS, templates), contacts, lists, campaigns, and lead submission.

---

## Authentication

1. Log in to 1app Grow and select the workspace that has your email/SMS/WhatsApp providers configured.
2. Open **Profile → API Tokens** and create a token.
3. Enable both permissions on the token:
   - **`write`** — required for sending messages and creating/updating contacts and lists
   - **`read`** — required for listing resources and checking message delivery status

If you see `"Invalid ability provided."`, your token is missing **`write`**. Recreate the token and check **read** + **write** (not legacy create/update/delete labels).

Send the token on every request:

```http
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Accept: application/json
```

The API uses the **currently selected workspace** on the user account that owns the token. Create separate tokens per workspace if you operate multiple tenants.

---

## Idempotency

Transactional send endpoints accept an idempotency key to prevent duplicate sends when your client retries:

- Header: `Idempotency-Key: unique-string-per-attempt`
- Or JSON body field: `"idempotency_key": "unique-string-per-attempt"`

If the same key is reused for the same workspace, the original message record is returned instead of sending again.

---

## Transactional messaging

All send endpoints require the **`write`** ability and are rate-limited to 300 requests per minute per token.

### WhatsApp OTP (recommended for 1app phone verification)

```
POST /messages/whatsapp/otp
```

Sends a one-time code using an **AUTHENTICATION** category WhatsApp template approved in Meta.

**Body**

| Field | Required | Description |
|-------|----------|-------------|
| `to` | yes | E.164 phone number, e.g. `+2348012345678` |
| `code` | yes | OTP string, max 15 characters (Meta authentication limit) |
| `template` | no* | Meta template name (`meta_template_name` in Grow) |
| `language` | no | Template language code, default `en` |
| `from` | no | Sender phone number exactly as shown in Grow (**WhatsApp → Numbers**), e.g. `+2348012345678` |
| `whatsapp_number_id` | no | Alternative to `from` — numeric ID from `GET /whatsapp-numbers` |
| `idempotency_key` | no | See [Idempotency](#idempotency) |

Use **`from`** or **`whatsapp_number_id`**, not both. If neither is set, the first active workspace number is used.

\*Required unless your workspace administrator has configured a default OTP template.

**Example**

```bash
curl -X POST "https://grow.1app.online/api/v1/messages/whatsapp/otp" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: verify-user-9912" \
  -d '{
    "to": "+2348012345678",
    "code": "482910",
    "template": "1app_verify",
    "language": "en"
  }'
```

**Prerequisites**

- Active WhatsApp number on the workspace (WhatsApp → Numbers)
- Approved **AUTHENTICATION** template synced in Grow (WhatsApp → Templates)

### WhatsApp sender number

Pass the sender as **`from`** using the phone number shown in Grow under **WhatsApp → Numbers** (international format, e.g. `+2348012345678`).

If `from` is not on your workspace, the API returns HTTP **422**:

```json
{
  "status": "failed",
  "error": "The sender phone number is not registered on this workspace."
}
```

Alternatively, pass **`whatsapp_number_id`** from `GET /whatsapp-numbers`. If neither is set, the first active number on the workspace is used.

```json
{
  "to": "+2348012345678",
  "code": "482910",
  "template": "1app_verify",
  "from": "+2349087654321"
}
```

The template must be approved on that specific number.

---

### SMS OTP

```
POST /messages/sms/otp
```

| Field | Required | Description |
|-------|----------|-------------|
| `to` | yes | E.164 phone number |
| `code` | yes | OTP string |
| `message` | no | Custom template; `:code` is replaced. Default: `Your verification code is :code` |
| `idempotency_key` | no | See [Idempotency](#idempotency) |

**Example**

```json
{
  "to": "+2348012345678",
  "code": "482910"
}
```

---

### Transactional email

```
POST /messages/email
```

| Field | Required | Description |
|-------|----------|-------------|
| `to` | yes | Recipient email |
| `subject` | yes | Email subject |
| `html` | yes | HTML body |
| `idempotency_key` | no | See [Idempotency](#idempotency) |

---

### Free-form SMS

```
POST /messages/sms
```

| Field | Required | Description |
|-------|----------|-------------|
| `to` | yes | E.164 phone number |
| `body` | yes | Message text (max 320 chars) |
| `idempotency_key` | no | See [Idempotency](#idempotency) |

---

### WhatsApp template (non-OTP)

```
POST /messages/whatsapp
```

| Field | Required | Description |
|-------|----------|-------------|
| `to` | yes | E.164 phone number |
| `template` | yes | Meta template name |
| `language` | no | Default `en` |
| `variables` | no | Array of template variable values in order |
| `from` | no | Sender phone as shown in Grow (see [WhatsApp sender number](#whatsapp-sender-number)) |
| `whatsapp_number_id` | no | Alternative sender selector by ID |
| `idempotency_key` | no | See [Idempotency](#idempotency) |

---

### Message status

```
GET /messages/{id}
```

Requires **`read`** ability. Returns delivery status for a message sent in the current workspace.

**Response**

```json
{
  "id": 42,
  "channel": "whatsapp",
  "to": "+2348012345678",
  "status": "sent",
  "provider_message_id": "wamid.xxx",
  "error": null,
  "idempotency_key": "verify-user-9912",
  "sent_at": "2026-06-05T12:00:00+00:00",
  "created_at": "2026-06-05T11:59:58+00:00"
}
```

`status` is one of: `queued`, `sent`, `failed`.

---

## Send response format

Successful send (HTTP **201**):

```json
{
  "id": 42,
  "channel": "whatsapp",
  "to": "+2348012345678",
  "status": "sent",
  "provider_message_id": "wamid.xxx",
  "sent_at": "2026-06-05T12:00:00+00:00"
}
```

Failed send (HTTP **422**):

```json
{
  "id": 43,
  "channel": "whatsapp",
  "to": "+2348012345678",
  "status": "failed",
  "error": "No active WhatsApp number configured for this workspace."
}
```

Common errors:

| Error | Fix |
|-------|-----|
| No active WhatsApp number | Connect a number under WhatsApp → Numbers |
| No active SMS/email provider | Add provider under SMS or Email settings |
| WhatsApp OTP template not configured | Pass `template` in the request body |
| OTP code must be 15 characters or fewer | Shorten the code for authentication templates |
| No workspace associated with this token | User must belong to a workspace; switch workspace and recreate token |

---

## Integrating from 1app

Typical phone verification flow:

1. User enters phone number in 1app.
2. 1app backend generates a 6-digit OTP and stores it (cache/DB) with expiry.
3. 1app backend calls `POST /messages/whatsapp/otp` (or `/messages/sms/otp`) on Grow with a unique `Idempotency-Key` per verification session.
4. User enters OTP in 1app; 1app validates locally.
5. Optionally poll `GET /messages/{id}` if you need provider-level confirmation.

**PHP (Laravel) example**

```php
use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.grow.api_token'))
    ->withHeaders(['Idempotency-Key' => "otp-{$userId}-".now()->timestamp])
    ->post(config('services.grow.base_url').'/messages/whatsapp/otp', [
        'to' => $phoneE164,
        'code' => $otp,
        'template' => config('services.grow.whatsapp_otp_template'),
        'language' => 'en',
    ]);

if ($response->failed()) {
    report($response->json('error'));
}
```

Suggested `.env` on 1app:

```env
GROW_API_BASE_URL=https://grow.1app.online/api/v1
GROW_API_TOKEN=your_sanctum_token
GROW_WHATSAPP_OTP_TEMPLATE=1app_verify
```

---

## Contacts & lists (CRUD)

| Endpoint | Methods | Ability |
|----------|---------|---------|
| `/contacts` | GET, POST | read / write |
| `/contacts/{id}` | GET, PUT, DELETE | read / write |
| `/lists` | GET, POST | read / write |
| `/lists/{id}` | GET, PUT, DELETE | read / write |
| `/lists/{id}/contacts` | GET | read |

---

## Campaigns (read-only)

| Endpoint | Methods | Ability |
|----------|---------|---------|
| `/campaigns` | GET | read |
| `/campaigns/{id}` | GET | read |
| `/campaigns/{id}/logs` | GET | read |

---

## Lead capture

```
POST /leads
```

Authenticated lead ingestion. Requires a valid API token.

| Field | Required | Description |
|-------|----------|-------------|
| `name` | yes | Contact name |
| `email` | no | Email address |
| `phone` | no | Phone number |
| `source` | no | `api`, `meta_ads`, or `tiktok_ads` |
| `form_slug` | no | Link submission to an existing lead form |
| `custom_fields` | no | Key-value object for extra data |

---

## Errors

All `/api/v1/*` responses are JSON. Stack traces and internal exception details are **never** returned.

### HTTP status codes

| HTTP | Meaning |
|------|---------|
| 401 | Missing or invalid token |
| 403 | Token lacks required ability — see `error` and `required_abilities` |
| 404 | Endpoint or resource not found (wrong workspace returns 404 for scoped resources) |
| 422 | Validation failure or business/send failure — see `error` and optional `errors` |
| 429 | Rate limit exceeded — see `retry_after_seconds` when present |
| 500 | Unexpected server error — generic message only |

### Response shapes

**Authentication (401)**

```json
{ "error": "Authentication required. Provide a valid API token: Authorization: Bearer {token}" }
```

**Missing permission (403)**

```json
{
  "error": "This endpoint requires the write permission on your API token. Create a new token with read + write enabled.",
  "required_abilities": ["write"]
}
```

**No workspace on token (422)**

```json
{
  "error": "No workspace associated with this token. Open Grow, switch to the correct workspace, then create a new API token."
}
```

**Validation (422)**

```json
{
  "error": "The given data was invalid.",
  "errors": { "name": ["The name field is required."] }
}
```

**Send failure (422)** — transactional endpoints

```json
{
  "id": 43,
  "channel": "whatsapp",
  "to": "+2348012345678",
  "status": "failed",
  "error": "The sender phone number is not registered on this workspace."
}
```

**Not found (404)**

```json
{ "error": "Resource not found." }
```

---

## Support

- Configure providers in **Settings** before calling send endpoints.
- Workspace admins: use the in-app **Developers** page for live base URL, template names, and Postman collection.
- Platform issues: contact your 1app Grow administrator.
