We use cookies that are strictly necessary to run this site, plus analytics cookies to understand how it's used. Analytics cookies are loaded only with your consent, per Israel's Privacy Protection Law. Learn more in our privacy policy
Vibrate API Skill · v2026-08-28
One copy-paste teaches ChatGPT, Claude, Claude Code, Cursor or Copilot the whole Vibrate SMS & WhatsApp API: every endpoint, field, error code and gotcha. Ask for the integration you need and get working code.
mkdir -p .claude/skills/vibrate-api curl -fsSL https://www.vibrate.co.il/vibrate-api-skill.md -o .claude/skills/vibrate-api/SKILL.md
prompt
Read https://www.vibrate.co.il/vibrate-api-skill.md and treat it as the authoritative reference for the Vibrate SMS & WhatsApp REST API (base URL https://api.vibrate.co.il). Use only the endpoints, fields, status codes and error codes documented there - never invent others. Then help me with the following task:
https://www.vibrate.co.il/vibrate-api-skill.md
Three steps, under a minute.
Copy the full skill below (or the short prompt if your assistant can open links).
Paste it as the first message in ChatGPT, Claude, Cursor or Claude Code. It becomes the assistant's reference for the whole API.
"Send an SMS from my Express server", "send this WhatsApp template to my list", "poll delivery status and store it". You get code that uses the real endpoints.
Plain Markdown. This is exactly what your AI reads.
mkdir -p .claude/skills/vibrate-api curl -fsSL https://www.vibrate.co.il/vibrate-api-skill.md -o .claude/skills/vibrate-api/SKILL.md
---
name: vibrate-api
description: Authoritative reference for the Vibrate SMS & WhatsApp Business REST API (api.vibrate.co.il). Use when writing code that sends SMS or WhatsApp template messages, manages distribution lists (campaigns) and contacts, or polls delivery status through Vibrate. Covers auth, every endpoint, request/response shapes, phone-number rules, billing units, error codes and known pitfalls.
version: 2026-08-28
source: https://www.vibrate.co.il/vibrate-api-skill.md
---
# Vibrate API Skill
You are helping a developer integrate the **Vibrate** messaging platform (Israel-focused SMS + WhatsApp Business API, vibrate.co.il). This document is the complete, authoritative reference. Follow it exactly:
- Use ONLY the endpoints, fields, headers, status codes and error codes listed here. Do not invent endpoints (there is no `/v1/sms/status`, no `/v1/whatsapp/text`, no `/v2`).
- Base URL: `https://api.vibrate.co.il` - all paths below are relative to it. Always HTTPS. JSON in, JSON out.
- Prefer the most specific endpoint (e.g. `sendBulk` for personalised messages to many recipients, `send` for one message to one or more recipients).
- When the user's language is Hebrew, keep message bodies in Hebrew and explain in Hebrew; keep code, field names and paths exactly as documented.
- Never use an em dash in generated copy; use a regular hyphen.
## 1. Authentication
- Every request needs an access token created in the Vibrate app at **Settings → Access Keys** (`https://www.vibrate.co.il/sms/tokens`).
- Send it in the `Authorization` header. The raw token is accepted; a `Bearer ` prefix is also accepted and stripped. `access_token` header is a legacy alias.
```http
Authorization: YOUR_ACCESS_TOKEN
Content-Type: application/json
```
- Auth failures return `401` with `code` one of `ACCESS_TOKEN_MISSING`, `ACCESS_TOKEN_INVALID`, `ACCESS_TOKEN_INACTIVE`, `USER_DELETED`, `USER_INACTIVE`.
- Tokens are per user account; all campaigns, senders, WhatsApp numbers and templates referenced in a request must belong to that account.
## 2. Response envelope
Success (2xx):
```json
{ "success": true, "data": { ... }, "message": "Human readable" }
```
Error (4xx/5xx):
```json
{
"message": "Insufficient SMS credits",
"code": "SMS_INSUFFICIENT_CREDITS",
"errors": ["..."],
"hints": "You need at least 3 SMS credits, but you only have 1 SMS credits"
}
```
- Branch on the HTTP status and on `code`, never on `message` text.
- Zod validation failures return `400` with `code: "VALIDATION_ERROR"` and `errors` = the Zod issue array (`path`, `message` per issue).
- Bodies over the limit (2 MB; 15 MB for `/v1/sms/sendBulk`) return `413 PAYLOAD_TOO_LARGE`.
## 3. Core rules (read before writing code)
### Phone numbers
- Israel is the implicit country. Accepted input forms, all normalised server-side to E.164 digits without `+`: `0501234567`, `050-123-4567`, `972501234567`, `+972501234567`, `+972 050-123-4567`.
- Foreign numbers must carry their country code with `+` or `00` (`+14155551234`, `00447911123456`) or already be in E.164 digits (`14155551234`).
- Pasted numbers often carry invisible bidi characters (U+200E, U+202C etc.). Strip non-digit / non-`+` characters client-side before sending.
- Recipients on the account's do-not-send (unsubscribe) registry are silently skipped at send time; this is by design and cannot be overridden through the API.
### Sender (SMS only)
- `sender` is REQUIRED on every SMS send and must exactly match an **active sender** on the account (an approved alphanumeric Sender ID such as `MySender`, or a phone number). Otherwise: `400 SMS_INVALID_SENDER`.
- Senders are created and approved in the Vibrate app, not via the API.
### SMS billing units
- 1 SMS unit = up to **256 characters** of the trimmed message, regardless of language or encoding (Hebrew and English cost the same). Units = `ceil(length / 256)`.
- Cost of a request = units × number of recipients. Credits are reserved atomically before queuing; if the balance is short the whole request is rejected with `400 SMS_INSUFFICIENT_CREDITS` and nothing is sent.
- Accounts may carry a rolling 24-hour message cap: `400 SMS_DAILY_LIMIT_EXCEEDED` (`hints` includes limit / used / remaining).
### Asynchronous delivery
- All send endpoints return **`202 Accepted`** once the batch is queued. A 202 means "accepted and billed", NOT "delivered". Track delivery with `GET /v1/sms/run/{runId}/delivery-status` or `GET /v1/sms/messages?runId=...`.
- Message statuses: `queued` → `in_progress` → `sent` → `delivered`, or `failed`. `statusHistory` on each message is an ordered array of `{ state, occurredAt, error?, errorCodes? }`.
### Idempotency (bulk SMS)
- `POST /v1/sms/sendBulk` accepts an optional `Idempotency-Key` header (1-255 chars, use a UUID). Retrying with the same key within 24 h replays the original 202 (response header `Idempotency-Replayed: true`) instead of sending and billing again. A retry while the first request is still running returns `409 CONFLICT`; retry after a few seconds. Failed requests release the key so a retry re-executes.
## 4. Endpoints
### 4.1 Account
#### GET /v1/user/info
Returns the account behind the token, including the SMS credit balance.
Response `200`:
```json
{ "success": true, "message": "User data retrieved successfully",
"data": { "email": "[email protected]", "name": "ישראל ישראלי", "status": "active", "smsAmount": 38 } }
```
Use `smsAmount` to pre-check credits before a large send.
### 4.2 SMS
#### POST /v1/sms/send
One message body to one or more recipients.
Body:
| field | type | required | notes |
|---|---|---|---|
| `recipients` | string[] | yes | non-empty; each a phone number (see rules) |
| `message` | string | yes | trimmed server-side; units = ceil(len/256) |
| `sender` | string | yes | active sender on the account |
| `campaignId` | string | no | tag the messages to a campaign for reporting |
```bash
curl -X POST https://api.vibrate.co.il/v1/sms/send \
-H "Authorization: YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"recipients":["0501231234"],"message":"שלום עולם","sender":"MySender"}'
```
Response `202`:
```json
{ "success": true, "message": "SMS batch queued successfully", "data": { "runId": "6d3c80fc-e2c6-4593-b787-cecafcf84f4b" } }
```
Errors: `400 VALIDATION_ERROR | SMS_NO_RECIPIENTS | SMS_INVALID_SENDER | SMS_DAILY_LIMIT_EXCEEDED | SMS_INSUFFICIENT_CREDITS`, `401`.
#### POST /v1/sms/sendBulk
Different message per recipient (personalised sends). One `sender` for the whole batch. Body limit 15 MB. Supports `Idempotency-Key`.
Body:
| field | type | required | notes |
|---|---|---|---|
| `messages` | { recipient: string, message: string }[] | yes | non-empty |
| `sender` | string | yes | active sender |
| `campaignId` | string | no | |
```bash
curl -X POST https://api.vibrate.co.il/v1/sms/sendBulk \
-H "Authorization: YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c2b7e-8d1a-4a4c-9d3e-0a1b2c3d4e5f" \
-d '{"sender":"MySender","messages":[{"recipient":"0501231234","message":"הודעה ראשונה"},{"recipient":"0505675678","message":"הודעה שנייה"}]}'
```
Response `202`: same shape as `/send` (`data.runId`). Errors as `/send` plus `400 INVALID_DATA_FORMAT` (bad Idempotency-Key), `409 CONFLICT`, `413 PAYLOAD_TOO_LARGE`.
#### GET /v1/sms/messages
Paginated message log for the account, newest first.
Query params (all optional): `status` (queued|in_progress|sent|delivered|failed), `runId`, `recipient` (substring match), `startDate`, `endDate` (ISO-8601, filter on createdAt), `page` (default 1), `limit` (default 10). `page`/`limit` must be positive integers or you get `400 MISSING_REQUIRED_FIELDS`.
Response `200`:
```json
{ "success": true, "message": "Messages retrieved successfully",
"data": {
"messages": [ { "id": "...", "recipient": "972501231234", "message": "...", "sender": "MySender", "runId": "...", "status": "delivered", "providerId": "...", "statusHistory": [ { "state": "queued", "occurredAt": "2025-06-16T21:25:19.903Z" } ], "createdAt": "...", "updatedAt": "..." } ],
"pagination": { "total": 120, "page": 1, "limit": 10, "totalPages": 12 } } }
```
#### GET /v1/sms/run/{runId}/delivery-status
Delivery summary for one send (the `runId` returned by any send endpoint). `404 RESOURCE_NOT_FOUND` if the run has no messages for this account.
Response `200`:
```json
{ "success": true, "message": "Run delivery status retrieved successfully",
"data": {
"runId": "...", "allDelivered": false,
"summary": { "total": 3, "delivered": 2, "pending": 1 },
"deliveryTimes": [ { "messageId": "...", "recipient": "972501231234", "deliveredAt": "2025-06-16T21:26:02.000Z", "status": "delivered" } ],
"nonDeliveredMessages": [ { "messageId": "...", "recipient": "972505675678", "status": "sent", "lastUpdated": "..." } ] } }
```
`nonDeliveredMessages` is omitted when everything is delivered. Poll with backoff (e.g. every 10-30 s); delivery receipts typically arrive within seconds to a few minutes.
### 4.3 Campaigns (distribution lists)
A **campaign** is a named contact list owned by the account. Contacts carry `name`, `phoneNumber` and free-form `additionalFields` used for personalisation.
#### GET /v1/sms/campaigns
Response `200`: `data` = array of `{ id, name, status, userId, createdAt, updatedAt }`.
#### POST /v1/sms/campaigns
Body: `{ "name": string }` (required, non-empty). Response `201`: `data` = the campaign object above.
#### GET /v1/sms/campaigns/{campaignId}/contacts
Response `200`: `data` = array of contacts `{ id, name, phoneNumber, additionalFields, status, campaignId, createdAt, updatedAt }`.
#### POST /v1/sms/campaigns/{campaignId}/contacts
Body:
| field | type | required |
|---|---|---|
| `phoneNumber` | string | yes |
| `name` | string | yes |
| `additionalFields` | object (string → any) | no |
```json
{ "name": "David Beckham", "phoneNumber": "0505975550", "additionalFields": { "first name": "David", "last name": "Beckham" } }
```
Response `201`: `data` = the created contact. `404` if the campaign is not yours.
#### DELETE /v1/sms/campaigns/{campaignId}/contacts/{contactId}
Response `200` `{ success: true, message: "Contact removed from campaign successfully" }`.
#### POST /v1/sms/campaigns/{campaignId}/run
Send one templated SMS to every **active** contact in the campaign; personalisation happens server-side per contact.
Body:
| field | type | required | notes |
|---|---|---|---|
| `sender` | string | yes | active sender |
| `message` | string | yes | template; `{{name}}` → contact.name, `{{<additionalField key>}}` → that field |
| `addUnsubscribe` | boolean | no (default false) | appends `<unsubscribeText> https://www.vibrate.co.il/r/<contactId>` to each message |
| `nameVar` | string | no | rename the placeholder that maps to contact.name (default `name`) |
| `unsubscribeText` | string | no | label before the unsubscribe link (default `Unsubscribe:`) |
```json
{ "sender": "MySender", "message": "שלום {{name}}, יש לך הזמנה חדשה!", "addUnsubscribe": true, "unsubscribeText": "להסרה:" }
```
Response `202`:
```json
{ "success": true, "message": "Campaign run queued successfully",
"data": { "runId": "...", "campaignId": "...", "recipientCount": 247, "totalSmsUnits": 247 } }
```
Errors: `400 VALIDATION_ERROR | SMS_INVALID_SENDER | CAMPAIGN_NO_ACTIVE_CONTACTS | SMS_DAILY_LIMIT_EXCEEDED | SMS_INSUFFICIENT_CREDITS`, `404 CAMPAIGN_NOT_FOUND`. The unsubscribe suffix counts toward the 256-char units.
### 4.4 WhatsApp
WhatsApp sends go through Meta's Cloud API. Only **approved templates** can be sent through this API (free-text replies are handled in the Vibrate inbox / chatbot, not here).
- `whatsappNumberId`: ID of a WhatsApp number connected to the account - copy it from **WhatsApp → Numbers** (`https://www.vibrate.co.il/whatsapp/numbers`).
- `templateId`: ID of an approved template - copy it from **WhatsApp → Templates** (`https://www.vibrate.co.il/whatsapp/templates`). Both are Vibrate IDs, not Meta IDs.
- Variables are positional: a template body "שלום {{1}}, ההזמנה {{2}} נשלחה" needs `bodyVariables: ["דוד", "ORD-12345"]`. Counts must match the template exactly.
- Header types: TEXT (may use `headerVariables`), IMAGE / VIDEO / DOCUMENT (require a publicly reachable `headerMediaUrl`; Meta fetches it).
#### POST /v1/whatsapp/template/send
Body:
| field | type | required | notes |
|---|---|---|---|
| `whatsappNumberId` | string | yes | |
| `templateId` | string | yes | |
| `recipient` | string | yes | phone, any accepted form |
| `bodyVariables` | string[] | no | one per `{{n}}` in the body, in order |
| `headerVariables` | string[] | no | for TEXT headers with variables |
| `headerMediaUrl` | string | no | public URL for IMAGE/VIDEO/DOCUMENT headers |
| `buttonVariables` | string[] | no | one per dynamic-URL button (`https://x.com/{{1}}`), replaces the placeholder only |
```bash
curl -X POST https://api.vibrate.co.il/v1/whatsapp/template/send \
-H "Authorization: YOUR_ACCESS_TOKEN" -H "Content-Type: application/json" \
-d '{"whatsappNumberId":"your-whatsapp-number-id","templateId":"your-template-id","recipient":"0501234567","bodyVariables":["דוד","ORD-12345"]}'
```
Response `200`:
```json
{ "success": true, "statusCode": 200, "message": "WhatsApp template sent successfully",
"data": { "messageId": "wamid.HBgLOTcyNTA...", "recipient": "972501234567" } }
```
Errors (`400`): `MISSING_REQUIRED_FIELDS`, `INVALID_TEMPLATE` / `WHATSAPP_TEMPLATE_NOT_FOUND` / `WHATSAPP_TEMPLATE_NOT_APPROVED`, `INVALID_PHONE`, `MISSING_VARIABLES` / `WHATSAPP_INVALID_VARIABLES`, `INVALID_MEDIA_URL`, `INVALID_WHATSAPP_NUMBER` / `WHATSAPP_NUMBER_NOT_FOUND`; `WHATSAPP_SEND_FAILED` / `WHATSAPP_GATEWAY_ERROR` for upstream Meta failures.
#### POST /v1/whatsapp/template/sendBulk
Send a template to every contact in a campaign (distribution list), mapping variables from contact fields. Runs in the background.
Body:
| field | type | required | notes |
|---|---|---|---|
| `whatsappNumberId` | string | yes | |
| `templateId` | string | yes | |
| `campaignId` | string | yes | the distribution list |
| `bodyMappings` | { type: "field" \| "custom", value: string }[] | no | positional; `field` reads `name` or `additionalFields.<key>` from each contact, `custom` is a fixed string |
| `headerMappings` | same shape | no | for TEXT header variables |
| `headerMediaUrl` | string | no | |
| `resumeBatchId` | string | no | re-run a previous batch, skipping contacts already sent/delivered/read |
```json
{ "whatsappNumberId": "your-whatsapp-number-id", "templateId": "your-template-id", "campaignId": "your-campaign-id",
"bodyMappings": [ { "type": "field", "value": "name" }, { "type": "custom", "value": "20% הנחה" } ] }
```
Response `202`:
```json
{ "success": true, "statusCode": 202, "message": "Bulk WhatsApp send started in background", "data": { "batchId": "batch_abc123", "totalCount": 2 } }
```
Keep `batchId`; pass it as `resumeBatchId` to safely retry a partially failed batch without double-sending.
## 5. Error code reference
| HTTP | code | meaning / fix |
|---|---|---|
| 400 | VALIDATION_ERROR | body failed schema; read `errors[]` |
| 400 | MISSING_REQUIRED_FIELDS | a required field or query param is missing/invalid |
| 400 | INVALID_DATA_FORMAT | e.g. malformed Idempotency-Key |
| 400 | SMS_NO_RECIPIENTS | `recipients` empty |
| 400 | SMS_INVALID_SENDER | `sender` missing or not an active sender on this account |
| 400 | SMS_INSUFFICIENT_CREDITS | top up credits; nothing was sent or charged |
| 400 | SMS_DAILY_LIMIT_EXCEEDED | 24 h cap reached; `hints` tells when it resets |
| 400 | CAMPAIGN_NO_ACTIVE_CONTACTS | list is empty or all contacts inactive/unsubscribed |
| 401 | ACCESS_TOKEN_MISSING / ACCESS_TOKEN_INVALID / ACCESS_TOKEN_INACTIVE | fix the Authorization header / create a new key |
| 401 | USER_INACTIVE / USER_DELETED | account problem; contact Vibrate support |
| 404 | CAMPAIGN_NOT_FOUND / RESOURCE_NOT_FOUND | wrong ID or belongs to another account |
| 409 | CONFLICT | same Idempotency-Key still in flight; retry shortly |
| 413 | PAYLOAD_TOO_LARGE | split the batch |
| 5xx | SERVER_ERROR / SMS_GATEWAY_ERROR / SMS_GATEWAY_TIMEOUT / WHATSAPP_GATEWAY_ERROR | transient; retry with backoff and (for bulk SMS) the same Idempotency-Key |
## 6. Recommended integration pattern
1. On startup / before big sends: `GET /v1/user/info` → check `smsAmount` ≥ recipients × units.
2. Normalise phones and de-duplicate recipients client-side.
3. Send with `/v1/sms/sendBulk` + `Idempotency-Key` (or `/send` for a single body). Store the returned `runId`.
4. Poll `GET /v1/sms/run/{runId}/delivery-status` with backoff until `allDelivered` or a timeout you choose (e.g. 30 min); persist per-recipient status.
5. Handle `400` codes as permanent (fix input), `409/5xx` as retryable.
6. For WhatsApp, verify the template is APPROVED and the variable counts match before sending; re-runs use `resumeBatchId`.
## 7. Things this API does NOT do
- No inbound-message or delivery **webhooks** for API customers (poll the delivery-status endpoint).
- No free-text WhatsApp sends, no sender/number/template creation, no credit purchase via API - all of that is done in the Vibrate web app (https://www.vibrate.co.il).
- No rate-limit header is returned; keep concurrency modest (a few parallel requests) and batch through `sendBulk` instead of looping `/send`.
## 8. Minimal client (TypeScript)
```ts
const BASE = "https://api.vibrate.co.il";
const headers = { Authorization: process.env.VIBRATE_TOKEN!, "Content-Type": "application/json" };
export async function sendSms(recipients: string[], message: string, sender: string) {
const res = await fetch(`${BASE}/v1/sms/send`, { method: "POST", headers, body: JSON.stringify({ recipients, message, sender }) });
const json = await res.json();
if (!res.ok) throw new Error(`${json.code}: ${json.message} (${json.hints ?? ""})`);
return json.data.runId as string;
}
export async function deliveryStatus(runId: string) {
const res = await fetch(`${BASE}/v1/sms/run/${runId}/delivery-status`, { headers });
const json = await res.json();
if (!res.ok) throw new Error(`${json.code}: ${json.message}`);
return json.data as { allDelivered: boolean; summary: { total: number; delivered: number; pending: number } };
}
```
---
Docs (login required): https://www.vibrate.co.il/docs/sms and https://www.vibrate.co.il/docs/whatsapp. Public copy of this skill: https://www.vibrate.co.il/vibrate-api-skill.md. Site index for AI: https://www.vibrate.co.il/llms.txt.
/v1/user/info/v1/sms/send/v1/sms/sendBulk/v1/sms/messages/v1/sms/run/{runId}/delivery-status/v1/sms/campaigns/v1/sms/campaigns/v1/sms/campaigns/{campaignId}/contacts/v1/sms/campaigns/{campaignId}/contacts/v1/sms/campaigns/{campaignId}/contacts/{contactId}/v1/sms/campaigns/{campaignId}/run/v1/whatsapp/template/send/v1/whatsapp/template/sendBulkA single Markdown document that describes the entire Vibrate REST API: authentication, every SMS and WhatsApp endpoint, request and response shapes, phone-number rules, billing units and error codes. You paste it into an AI assistant so it can write a correct integration without guessing.
Any assistant that accepts text: ChatGPT, Claude, Claude Code, Cursor, GitHub Copilot, Gemini and others. Claude Code and Cursor can also install it as a local skill or rule so it loads automatically.
Yes. Create a free account, add SMS credits or connect a WhatsApp Business number, then create an access key under Settings → Access Keys. The key goes in the Authorization header of every request.
Send single or bulk SMS, run personalised campaigns to distribution lists, manage contacts, poll delivery status per message, and send approved WhatsApp Business templates with variables and media to one recipient or a whole list.
Yes. It is generated from the same source as the API documentation and served from a fixed URL, so re-fetching it always gives the current version.
Copy one prompt and ChatGPT, Claude, Claude Code or Cursor will know the entire Vibrate REST API: send SMS and WhatsApp templates, manage lists, poll delivery. Israel's SMS & WhatsApp Business API.