Docs / API reference
REST API · v1

Repeat Grow API

Send WhatsApp messages, sync contacts, read conversations, launch broadcasts, and get real-time events from your own apps and scripts.

For your Repeat Grow account at repeatgrow.com · Manage keys at repeatgrow.com/settings?tab=api

Base URL https://repeatgrow.com/api/v1OpenAPI spec

01Quickstart

Send your first WhatsApp message through the API in about two minutes.

  1. Create an API keyLog in at repeatgrow.com and open Settings → API keys, then click New API key. Only admins and owners can do this.
  2. Choose scopes and copy the keyTick messages:send for this example. The key is shown only once, so store it somewhere safe.
  3. Send a messageSet the key as an environment variable and make the request.
Terminal
export REPEATGROW_API_KEY="RG-XXXX-..."

# Check the key works
curl https://repeatgrow.com/api/v1/me -H "Authorization: Bearer $REPEATGROW_API_KEY"
curl -X POST https://repeatgrow.com/api/v1/messages \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+14155550123", "text": "Hi 👋 your order has shipped." }'
WhatsApp only delivers free-form text and media within 24 hours of the customer's last message. To start a new conversation, send an approved template.

02Authentication

Send your key as a bearer token on every request: Authorization: Bearer RG-XXXX-…

  • Each key belongs to one account and can only do what its scopes allow.
  • Repeat Grow stores only a hash, so a lost key can't be shown again. Revoke it and create a new one in repeatgrow.com/settings?tab=api.
  • Keep keys on your server. Never put them in a website or mobile app.
ScopeAllows
messages:sendSend WhatsApp messages
messages:readRead messages and their delivery status
contacts:readList and read contacts
contacts:writeCreate and update contacts
conversations:readList and read conversations
broadcasts:sendLaunch broadcast campaigns
webhooks:manageRegister and manage outbound event webhooks
templates:readList message templates and their review status
templates:writeCreate message templates and submit them to Meta

A key with no scopes can still call GET /me to check it works.

03Errors & rate limits

Every response uses one of two shapes. Branch on error.code; the message is for people and may change.

Response shapes
// Success
{ "data": { ... } }

// Error
{ "error": { "code": "forbidden", "message": "This API key is missing the 'messages:send' scope" } }
StatusCodeMeaning
400bad_requestMalformed input. The message says which field.
400whatsapp_not_configuredConnect WhatsApp in Settings first.
401unauthorizedMissing, invalid, revoked, or expired key.
403forbiddenThe key doesn't have the scope this endpoint needs.
404not_foundNo such resource in your account.
429rate_limitedOver 120 requests a minute. Wait Retry-After seconds.
500internalServer error. Safe to retry with backoff.
502meta_errorWhatsApp rejected the send, e.g. outside the 24-hour window.

Each key can make 120 requests per minute. A 429 response includes Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.

04Pagination

List endpoints return up to limit items (default 50, max 100) and a meta.next_cursor. Pass it back as cursor for the next page. When it's null, you've reached the end.

Example
GET /api/v1/contacts?limit=100
→ { "data": [ ... ], "meta": { "next_cursor": "eyJ..." } }

GET /api/v1/contacts?limit=100&cursor=eyJ...
→ { "data": [ ... ], "meta": { "next_cursor": null } }

05Endpoints

All paths are relative to https://repeatgrow.com/api/v1.

Account

Verify a key and discover its scopes.

GET/api/v1/me

Get the current key and account

Returns the account the key is bound to and its scopes. Needs no scope — use it to check a key works.

Request
curl "https://repeatgrow.com/api/v1/me" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
Response · 200
{
  "data": {
    "account": {
      "id": "7d0c…",
      "name": "Acme Inc"
    },
    "key": {
      "id": "1f2a…",
      "scopes": [
        "messages:send"
      ]
    }
  }
}

Messages

Send WhatsApp messages.

POST/api/v1/messagesmessages:send

Send a WhatsApp message

Send a message to a phone number in E.164 format. The contact and conversation are found or created automatically. Free-form messages (text/media) only deliver inside WhatsApp's 24-hour customer-service window; outside it, send an approved template.

Request
curl -X POST "https://repeatgrow.com/api/v1/messages" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155550123",
    "type": "text",
    "text": "Hi 👋 your order has shipped."
  }'
Response · 201
{
  "data": {
    "message_id": "0b6e…",
    "whatsapp_message_id": "wamid.HBgM…",
    "conversation_id": "5c1d…",
    "contact_id": "9a3f…",
    "contact_created": true
  }
}

Contacts

Create, list, read, and update contacts.

GET/api/v1/contactscontacts:read

List contacts

Newest first, paginated.

  • limit queryPage size (default 50, max 100).
  • cursor queryOpaque meta.next_cursor from the previous page. Pass it back verbatim.
  • search queryMatch on name or phone.
  • tag queryOnly contacts with this tag id.
Request
curl "https://repeatgrow.com/api/v1/contacts" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
POST/api/v1/contactscontacts:write

Create a contact

Find-or-create by phone: an existing contact with the same number returns 200, a new one 201. Tags are given by name and created if missing.

Request
curl -X POST "https://repeatgrow.com/api/v1/contacts" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+14155550123",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "company": "Acme",
    "tags": [
      "vip",
      "website-lead"
    ]
  }'
GET/api/v1/contacts/{id}contacts:read

Get a contact

  • id pathContact id.
Request
curl "https://repeatgrow.com/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
PATCH/api/v1/contacts/{id}contacts:write

Update a contact

Only the fields you send change. Sending tags replaces the contact's tags.

  • id pathContact id.
Request
curl -X PATCH "https://repeatgrow.com/api/v1/contacts/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "company": "Acme Ltd",
    "tags": [
      "vip"
    ]
  }'

Conversations

Read conversations and their messages.

GET/api/v1/conversationsconversations:read

List conversations

Newest first, paginated. Each conversation embeds its contact.

  • limit queryPage size (default 50, max 100).
  • cursor queryOpaque meta.next_cursor from the previous page. Pass it back verbatim.
  • status query
  • contact_id query
Request
curl "https://repeatgrow.com/api/v1/conversations" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
GET/api/v1/conversations/{id}conversations:read

Get a conversation

  • id pathConversation id.
Request
curl "https://repeatgrow.com/api/v1/conversations/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
GET/api/v1/conversations/{id}/messagesmessages:read

List messages in a conversation

Newest first, paginated.

  • id pathConversation id.
  • limit queryPage size (default 50, max 100).
  • cursor queryOpaque meta.next_cursor from the previous page. Pass it back verbatim.
Request
curl "https://repeatgrow.com/api/v1/conversations/{id}/messages" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"

Broadcasts

Launch template broadcasts and track progress.

POST/api/v1/broadcastsbroadcasts:send

Launch a template broadcast

Sends an approved template to up to 1,000 recipients per request. Returns immediately while sends run in the background — poll GET /broadcasts/{id} for progress. Invalid numbers and contacts who replied STOP (opted out) are dropped and counted as rejected.

Request
curl -X POST "https://repeatgrow.com/api/v1/broadcasts" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Diwali offer",
    "template_name": "festive_offer",
    "template_language": "en_US",
    "recipients": [
      {
        "to": "+919876543210",
        "params": [
          "Priya"
        ]
      },
      {
        "to": "+919876543211",
        "params": [
          "Rahul"
        ]
      }
    ]
  }'
Response · 202
{
  "data": {
    "broadcast_id": "3e7b…",
    "status": "sending",
    "total_recipients": 2,
    "accepted": 2,
    "rejected": 0
  }
}
GET/api/v1/broadcasts/{id}broadcasts:send

Get broadcast progress

status moves sending → sent; delivered/read counts keep rising as WhatsApp reports them.

  • id pathBroadcast id.
Request
curl "https://repeatgrow.com/api/v1/broadcasts/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"

Templates

Create WhatsApp message templates and follow their review by Meta.

GET/api/v1/templatestemplates:read

List message templates

Your templates and their review status, optionally filtered by names (comma-separated, up to 50). Status is kept current by Meta's review webhook.

  • names queryComma-separated template names, up to 50.
Request
curl "https://repeatgrow.com/api/v1/templates" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
Response · 200
{
  "data": [
    {
      "id": "6f1e…",
      "name": "birthday_gift",
      "language": "en",
      "category": "MARKETING",
      "status": "APPROVED",
      "rejection_reason": null,
      "body_text": "Happy birthday, {{1}}! Your treat: {{2}}.",
      "updated_at": "2026-10-12T10:00:00.000Z"
    }
  ]
}
POST/api/v1/templatestemplates:write

Create a template and submit it to Meta

Submits the template to Meta for review. Safe to call again: the same name, language and wording returns the current template with 200 and nothing is resubmitted; a REJECTED or DRAFT template is resubmitted; an approved or pending template with different wording returns 409 conflict (use a new name). AUTHENTICATION templates are not supported.

Request
curl -X POST "https://repeatgrow.com/api/v1/templates" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "birthday_gift",
    "category": "MARKETING",
    "language": "en",
    "body_text": "Happy birthday, {{1}}! Your treat: {{2}}.",
    "sample_values": {
      "body": [
        "Priya",
        "Free dessert"
      ]
    }
  }'
Response · 201
{
  "data": {
    "id": "6f1e…",
    "name": "birthday_gift",
    "language": "en",
    "category": "MARKETING",
    "status": "PENDING"
  }
}

Webhooks

Register endpoints that receive events from your account.

GET/api/v1/webhookswebhooks:manage

List webhook endpoints

Never returns signing secrets.

Request
curl "https://repeatgrow.com/api/v1/webhooks" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
POST/api/v1/webhookswebhooks:manage

Register a webhook endpoint

The URL must be public https://. The response contains the signing secret once — store it to verify the X-Wacrm-Signature header on deliveries.

Request
curl -X POST "https://repeatgrow.com/api/v1/webhooks" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/repeatgrow",
    "events": [
      "message.received"
    ]
  }'
GET/api/v1/webhooks/{id}webhooks:manage

Get a webhook endpoint

  • id pathWebhook endpoint id.
Request
curl "https://repeatgrow.com/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
PATCH/api/v1/webhooks/{id}webhooks:manage

Update a webhook endpoint

Change url, events, or is_active. Re-enabling an endpoint resets its failure counter.

  • id pathWebhook endpoint id.
Request
curl -X PATCH "https://repeatgrow.com/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": true
  }'
DELETE/api/v1/webhooks/{id}webhooks:manage

Delete a webhook endpoint

  • id pathWebhook endpoint id.
Request
curl -X DELETE "https://repeatgrow.com/api/v1/webhooks/{id}" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
Response · 200
{
  "data": {
    "id": "2b9c…",
    "deleted": true
  }
}

Connect

For partner apps: exchange a "Connect RepeatGrow" code for a key, and disconnect.

POST/api/v1/connect/token

Exchange a Connect code for an API key

Server to server, for registered partner apps only. After the account owner clicks Allow on /connect/{client}, exchange the one-time code (valid 5 minutes, single use) for a scoped API key. Authenticates with the client secret, not an API key. The key is returned once and never shown again.

Request
curl -X POST "https://repeatgrow.com/api/v1/connect/token" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "restrogrow",
    "client_secret": "rgcs_…",
    "code": "k3J…",
    "redirect_uri": "https://restrogrow.com/main/api/repeatgrow_connect.php"
  }'
Response · 200
{
  "data": {
    "api_key": "RG-XXXX-…",
    "account": {
      "id": "7d0c…",
      "name": "Spice Garden"
    },
    "phone_number": "+91 98765 43210",
    "scopes": [
      "messages:send",
      "templates:read",
      "templates:write"
    ]
  }
}
POST/api/v1/keys/revoke-self

Revoke the calling API key

Any valid key can revoke itself, e.g. when a partner app disconnects. Needs no scope.

Request
curl -X POST "https://repeatgrow.com/api/v1/keys/revoke-self" \
  -H "Authorization: Bearer $REPEATGROW_API_KEY"
Response · 200
{
  "data": {
    "revoked": true
  }
}

Full request and response schemas are in the OpenAPI spec.

06Webhooks

Get notified the moment something happens instead of polling.

Register an HTTPS endpoint with POST /api/v1/webhooks (scope webhooks:manage). Repeat Grow POSTs to it when these events happen:

EventFires when
message.receivedAn inbound message was received from a contact
message.status_updatedA message you sent changed delivery status (sent/delivered/read/failed)
conversation.createdA new conversation was opened
Delivery body
{
  "id": "8f3c…",               // unique per delivery: dedupe on this
  "event": "message.received",
  "occurred_at": "2026-07-01T12:00:00.000Z",
  "account_id": "…",
  "data": {
    "conversation_id": "…",
    "contact_id": "…",
    "whatsapp_message_id": "wamid.…",
    "content_type": "text",
    "text": "Hi 👋"
  }
}

Verify the signature

Each delivery has an X-Wacrm-Signature: t=<unix>,v1=<hex> header, where v1 is an HMAC-SHA256 of {t}.{raw body} using the whsec_… secret you got when creating the endpoint. Events also carry X-Wacrm-Event and X-Wacrm-Webhook-Id.

import crypto from "node:crypto";

// Pass the RAW request body (a string), not re-serialized JSON.
export function verifyRepeatGrowSignature(rawBody, header, secret) {
  const match = /t=(\d+),v1=([0-9a-f]+)/.exec(header ?? "");
  if (!match) return false;
  const [, t, v1] = match;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // replay guard
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
  • Reply with any 2xx quickly. Each event is sent once with a short timeout, and redirects aren't followed.
  • Events can repeat or arrive out of order, so dedupe on id.
  • After repeated failures an endpoint is switched off. Turn it back on with PATCH and { "is_active": true }.
  • The URL must be public HTTPS. Private and local addresses are refused.

07OpenAPI spec

The full API is described in a public OpenAPI 3.1 file at https://repeatgrow.com/api/v1/openapi.json. Import it into Postman, Insomnia, or Swagger UI, or generate a typed client.

Terminal
# Download the spec
curl -o repeatgrow-openapi.json https://repeatgrow.com/api/v1/openapi.json

# Generate TypeScript types
npx openapi-typescript https://repeatgrow.com/api/v1/openapi.json -o repeatgrow.d.ts

Building something?

Tell us what you're integrating and we'll help you get it working.