Send a review request
POST/api/v1/review-requests
reviews:sendIdempotentSends one contact a message asking for a review, with a link that is theirs alone, and returns the request.
The message goes out on the channel the contact most recently used. On WhatsApp, outside the 24-hour window, it is sent as the review template the account configured in Reviews → Settings; inside the window, and on Messenger and Instagram, it is a plain message. Leave form_id out to use the account’s default form.
When it cannot be sent the answer is 422 unprocessable and reason says why: contact_not_found, form_not_found, no_review_form (the account has no active form), form_required (several forms and no default — send form_id), template_required (the WhatsApp template is missing or not approved), outside_window (Messenger or Instagram, more than 24 hours since the customer wrote), no_conversation (no way to reach the contact) or send_failed. A request that failed to send is recorded as failed and does not count toward the monthly limit; reaching that limit returns 402 plan_limit_reached.
Send an Idempotency-Key header so a retry does not message the customer twice.
Body
application/json| Name | Type | Description |
|---|---|---|
contact_idrequired | string (uuid) | Who to ask |
form_idoptional | string (uuid) | Which form to send. Defaults to the account’s default form |
service_hintoptional | string | What they experienced, for the template body, e.g. "your website launch" |
curl -X POST "https://your-crm.example.com/api/v1/review-requests" \
-H "Authorization: Bearer $RAABTA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-3b4d-4c5e-8f7a-9b0c1d2e3f4a" \
-d '{
"contact_id": "9d7e4c11-5a2b-4f7c-8e3d-2a1b0c9d8e7f",
"service_hint": "your website launch"
}'{
"data": {
"id": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
"contact_id": "9d7e4c11-5a2b-4f7c-8e3d-2a1b0c9d8e7f",
"form_id": "f0a1b2c3-d4e5-4f6a-8b7c-9d0e1f2a3b4c",
"conversation_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"channel": "whatsapp",
"source": "api",
"status": "sent",
"review_url": "https://raabtahq.online/r/3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c",
"sent_at": "2026-09-02T10:05:02.000Z",
"clicked_at": null,
"completed_at": null,
"error_message": null,
"created_at": "2026-09-02T10:05:00.000Z",
"updated_at": "2026-09-02T10:05:02.000Z"
}
}Response
Wrapped in { data: … }| Name | Type | Description |
|---|---|---|
idrequired | string (uuid) | UUID |
contact_idrequired | string (uuid) | null | Who was asked; null if the contact was since deleted |
form_idrequired | string (uuid) | The form the link opens |
conversation_idrequired | string (uuid) | null | The thread the request was sent in |
channelrequired | string (enum) | The channel it was sent on: the one the contact most recently usedwhatsappmessengerinstagramwebchatpersonal_whatsappemail |
sourcerequired | string (enum) | Where it came from: a member, a bulk send, an automation, the assistant, or this APImanualbulkautoaiapi |
statusrequired | string (enum) | pending, then sent or failed, then clicked when the link is opened and completed when a review is submittedpendingsentfailedclickedcompleted |
review_urlrequired | string | The customer’s own link. Anyone holding it can submit the review, once |
sent_atrequired | string (date-time) | null | When the message went out |
clicked_atrequired | string (date-time) | null | When the customer first opened the link |
completed_atrequired | string (date-time) | null | When the review was submitted |
error_messagerequired | string | null | Why it failed, when status is failed |
created_atrequired | string (date-time) | ISO 8601 timestamp |
updated_atrequired | string (date-time) | ISO 8601 timestamp |
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The request could not be parsed: malformed JSON, an invalid cursor, or a query parameter of the wrong shape. |
| 400 | validation_error | The body or query failed validation. `details` lists each failing field with a `path` and a `message`. |
| 401 | unauthorized | No usable API key: the Authorization header is missing or malformed, or the key is unknown, revoked or expired. The three are deliberately indistinguishable. |
| 402 | plan_limit_reached | The account has reached a plan limit for this resource. `details[0].limit` is the cap; ask the account owner to raise it. |
| 403 | forbidden | The key is valid but lacks the scope this endpoint requires, or the request came from an address outside the key’s IP allowlist. The message says which. |
| 403 | account_suspended | The account this key belongs to is suspended. Rotating the key will not help; contact support. |
| 409 | conflict | The request conflicts with current state: a duplicate phone number on create, an invalid state transition, or an idempotent request that is still in flight. |
| 422 | unprocessable | The request was well-formed but cannot be carried out. `reason` is a stable string saying why (for example `outside_window` or `stage_not_in_pipeline`). |
| 429 | rate_limited | The per-key budget, or the per-IP budget for failed authentication, is exhausted. Honour `Retry-After` before retrying. |
| 500 | internal | Something failed on our side. Safe to retry with the same Idempotency-Key; quote `request_id` if it persists. |