RaabtaHQ
Getting started

Getting started

Create an API key, verify it, create a contact and send a WhatsApp message — the RaabtaHQ REST API in four calls, with curl, Node and Python samples.

terminal — raabtahq api
$
curl "https://raabtahq.online/api/v1/contacts?limit=20&q=ayesha" \
-H "Authorization: Bearer raabta_hq_live_••••"
HTTP/1.1 200 OK
{
"data": [
{
"id": "9d7e4c11-5a2b-4f7c-8e3d-2a1b0c9d8e7f",
"name": "Ayesha Khan",
"phone": "+971501234567",
"email": "ayesha@example.com",
"company": "Khan Trading LLC",
"avatar_url": null,
"tags": […],
"custom_fields": […],
"created_at": "2026-09-01T08:15:00.000Z",
"updated_at": "2026-09-02T10:00:00.000Z"
}
],
"meta": {"next_cursor":null,"has_more":false,"limit":20}
}
# 200 OK · 42ms
# x-ratelimit-remaining: 59 / 60

Create an API key

Keys are created in the dashboard under Settings → API keys by an account admin or owner. Give the key a name that says what will use it, tick the scopes it needs and nothing more, and optionally an expiry, an IP allowlist and a lower rate limit.

The full key is shown once. It starts with raabta_hq_live_ followed by 43 random characters, and the server stores only a hash of it — there is no way to see it again. Put it in your environment as RAABTA_API_KEY; every sample on this site reads it from there.

Lost a key?

Regenerate it from the same screen. The key keeps its name, scopes and settings, gets a new secret, and the old secret stops working on its very next request.

Verify it works

GET /me needs a valid key and no scope, so it is the first call to make: it exercises the whole path — bearer parsing, the hash lookup, liveness, the rate limiter, the envelope — and tells you which account the key is bound to and what it may do.

Request
curl "https://your-crm.example.com/api/v1/me" \
  -H "Authorization: Bearer $RAABTA_API_KEY"
Response · 200 OK
{
  "data": {
    "account": {
      "id": "3f9a1c2e-5b7d-4e8f-9a0b-1c2d3e4f5a6b",
      "name": "Acme Clinic"
    },
    "key": {
      "id": "7c1e2d3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
      "name": "Zapier automation",
      "scopes": [
        "contacts:write",
        "messages:send"
      ],
      "effective_scopes": [
        "contacts:read",
        "contacts:write",
        "messages:send",
        "templates:read"
      ],
      "expires_at": "2026-12-01T00:00:00.000Z",
      "rate_limit_per_minute": 60
    }
  }
}

effective_scopes is the granted scopes plus the ones they imply — a key with contacts:write can also read contacts. The authentication guide has the full table.

Create a contact

A contact needs at least one of a phone, a name or an email. Phone numbers are E.164 and deduplicated by digits, so creating a contact whose number already exists returns 409 conflict with the existing id in details rather than a second record.

Request
curl -X POST "https://your-crm.example.com/api/v1/contacts" \
  -H "Authorization: Bearer $RAABTA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
  -d '{
    "phone": "+971501234567",
    "name": "Ayesha Khan",
    "email": "ayesha@example.com",
    "company": "Khan Trading LLC",
    "tag_ids": [
      "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
    ],
    "custom_fields": {
      "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d": "Instagram ad"
    }
  }'
Response · 201 Created
{
  "data": {
    "id": "9d7e4c11-5a2b-4f7c-8e3d-2a1b0c9d8e7f",
    "name": "Ayesha Khan",
    "phone": "+971501234567",
    "email": "ayesha@example.com",
    "company": "Khan Trading LLC",
    "avatar_url": null,
    "tags": [
      {
        "id": "1c2d3e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
        "name": "VIP",
        "color": "#10b981"
      }
    ],
    "custom_fields": [
      {
        "field_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
        "name": "Source",
        "value": "Instagram ad"
      }
    ],
    "created_at": "2026-09-01T08:15:00.000Z",
    "updated_at": "2026-09-02T10:00:00.000Z"
  }
}

Every create endpoint accepts an Idempotency-Key header. Send one and a retried request returns the first result instead of creating twice — see Idempotency.

Send a message

POST /messages takes a to target that is either an existing conversation_id or a phone on WhatsApp — in which case the contact and the thread are found or created for you.

Request
curl -X POST "https://your-crm.example.com/api/v1/messages" \
  -H "Authorization: Bearer $RAABTA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
  -d '{
    "type": "text",
    "to": {
      "phone": "+971501234567",
      "channel": "whatsapp"
    },
    "text": "Your appointment is confirmed for Saturday at 11:00."
  }'
Response · 201 Created
{
  "data": {
    "id": "0c1d2e3f-4a5b-4c6d-8e7f-8a9b0c1d2e3f",
    "conversation_id": "4e5f6a7b-8c9d-4e0f-a1b2-c3d4e5f6a7b8",
    "direction": "outbound",
    "sender_type": "bot",
    "type": "text",
    "text": "Your appointment is confirmed for Saturday at 11:00.",
    "media_url": null,
    "template_name": null,
    "status": "sent",
    "channel_message_id": "wamid.HBgLOTcxNTAxMjM0NTY3FQIAERgSN0Y4MjA1QzYwRjkzMDAyMzRCAA==",
    "reply_to_message_id": null,
    "created_at": "2026-09-02T10:00:00.000Z"
  }
}

The 24-hour window

WhatsApp only delivers free-form text and media within 24 hours of the customer's last message. Outside it the call fails with 422 unprocessable and reason: outside_window; send an approved template instead (type: "template").

Where next

  1. The API reference — every endpoint with its parameters, a worked example and the errors it can return.
  2. Errors and rate limits — what to branch on and when to back off.
  3. Pagination — every list endpoint uses the same cursor.
  4. Webhooks — get events pushed to you instead of polling.
  5. The OpenAPI 3.1 document — import it into Postman, Insomnia or a client generator.

Most integrations start with these three:

Or browse by resource: contacts, conversations, messages, broadcasts, pipelines & deals.