MessagingSMS campaigns
Messaging
SMS campaigns
Preview, send and track SMS campaigns.
SMS campaigns spend the business's SMS balance and text only customers who agreed to offers by SMS. Every message ends with a one-tap opt-out link. Sending needs sms:write, which no preset grants. The first campaign and large ones wait for an operator's review.
No answer ever carries a customer's name or phone.
Paths are relative to https://app.revisit.cards/v1/developer.
Endpoints
- GETRead whether SMS is open, the balance and the price
/sms/access - POSTPreview who a text would reach and what it would cost
/sms/campaigns/preview - GETList recent SMS campaigns and how each went
/sms/campaigns - POSTCreate an SMS campaign
/sms/campaigns - GETRead one SMS campaign and where it stands
/sms/campaigns/{id} - GETRead how each message of a campaign went
/sms/campaigns/{id}/messages - POSTCancel an SMS campaign before it goes out
/sms/campaigns/{id}/cancel
Read whether SMS is open, the balance and the price
/sms/accessScopesms:read
Requires sms:read. Answers even while SMS is suspended, so the owner learns why: state is approved or suspended (with the operator's suspendedReason), paused is the operator's kill switch, available is false on a self-hosted deployment. balance is the plan's segments this cycle (pool) plus bought credit; ratePoisha is one segment's price, VAT included. A test key reads its live owner's balance and spends none of it.
Returns 200
availablebooleanRequiredbalanceobjectRequiredbalance.creditintegerRequiredbalance.poolintegerRequiredbalance.totalintegerRequiredpausedbooleanRequiredratePoishaintegerRequiredstateenumRequired- One of
approved,suspended. suspendedReasonstring or nullRequired
curl "https://app.revisit.cards/v1/developer/sms/access" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"available": false,
"balance": {
"credit": 250,
"pool": 250,
"total": 12
},
"paused": false,
"ratePoisha": 40,
"state": "approved",
"suspendedReason": null
},
"requestId": "b4807bbf-7301-4c2f-bf37-947711fb618b",
"success": true
}Preview who a text would reach and what it would cost
/sms/campaigns/previewScopesms:read
Requires sms:read. Nothing is held or sent. The same preview as the composer: the exact text a customer gets (the opt-out link is a stand-in), segments, reachable customers (those who agreed to offers by SMS and have not opted out), notReachable and alreadyTexted as counts only, costPoisha, the balance on the day it would go out, missing segments, content issues, and whether it waits for an operator (review). A customer's number is never returned. A key limited to several branches or cards must name one with branchId and programId.
Body
audienceobjectaudience.kindenumRequired- One of
all,broadcast,segment,individual,location. audience.segmentenum- One of
lapsed_7,lapsed,one_away,reward_ready,new_week,champions. audience.membershipIdsuuid[]audience.branchIduuidbodystringRequired- The shop's words. The shop name is put in front and the opt-out link after it.
branchIduuidnoWalletOnlyboolean- Only customers who have not added the card to a wallet.
programIduuidscheduledFordate-time- Moved into the 9:00 to 21:00 Dhaka window.
Returns 200
alreadyTextedintegerRequiredbalanceobjectRequiredbalance.creditintegerRequiredbalance.poolintegerRequiredbalance.totalintegerRequiredcharactersLeftintegerRequiredcostPoishaintegerRequiredencodingenumRequired- One of
GSM_7BIT,UCS2. issuesobject[]Requiredissues[].codeenumRequired- One of
banned_topic,empty,link,no_bangla,too_long. issues[].messagestringRequiredmissingintegerRequirednotReachableintegerRequiredopensAtdate-timeRequiredratePoishaintegerRequiredreachableintegerRequiredreviewbooleanRequiredsegmentsintegerRequiredtextstringRequiredtotalSegmentsintegerRequired
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns/preview" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"body": "Your 10th chai is on us this week."
}'{
"data": {
"alreadyTexted": 0,
"balance": {
"credit": 250,
"pool": 250,
"total": 12
},
"charactersLeft": 92,
"costPoisha": 40,
"encoding": "GSM_7BIT",
"issues": [
{
"code": "banned_topic",
"message": "Your 10th chai is on us this week."
}
],
"missing": 0,
"notReachable": 9,
"opensAt": "2026-11-03T08:41:12.000Z",
"ratePoisha": 40,
"reachable": 125,
"review": false,
"segments": 1,
"text": "Collect 10, get 1 free",
"totalSegments": 120
},
"requestId": "7c5b8869-3f29-4cd0-83a6-f3afba744535",
"success": true
}List recent SMS campaigns and how each went
/sms/campaignsScopesms:read
Requires sms:read. Newest first, at most limit (50). Only campaigns of the branches and cards the key may use. counts is per outcome (accepted is handed to the carrier and not reported yet), costPoisha is the accepted segments.
Query
limitinteger- 1 to 50. Default
20. programIduuidbranchIduuid
Returns 200
itemsobject[]Requireditems[].bodystringRequireditems[].branchIdstring or nullRequireditems[].canceledReasonstring or nullRequireditems[].costPoishaintegerRequireditems[].countsobjectRequireditems[].counts.acceptedintegerRequireditems[].counts.deliveredintegerRequireditems[].counts.notDeliveredintegerRequireditems[].counts.queuedintegerRequireditems[].counts.rejectedintegerRequireditems[].counts.skippedintegerRequireditems[].counts.unknownintegerRequireditems[].createdAtdate-timeRequireditems[].iduuidRequireditems[].namestringRequireditems[].programIdstring or nullRequireditems[].recipientsintegerRequireditems[].reviewNotestring or nullRequireditems[].segmentsPerMessageintegerRequireditems[].sendAtdate-timeRequireditems[].statusenumRequired- One of
pending_review,queued,sending,sent,canceled,rejected.
curl "https://app.revisit.cards/v1/developer/sms/campaigns" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"items": [
{
"body": "Your 10th chai is on us this week.",
"branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
"canceledReason": null,
"costPoisha": 40,
"counts": {
"accepted": 100,
"delivered": 118,
"notDelivered": 2,
"queued": 0,
"rejected": 1,
"skipped": 3,
"unknown": 0
},
"createdAt": "2026-11-03T08:41:12.000Z",
"id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
"name": "Chai card",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"recipients": 125,
"reviewNote": "The customer used the reward before the refund",
"segmentsPerMessage": 1,
"sendAt": "2026-11-03T08:41:12.000Z",
"status": "pending_review"
}
]
},
"requestId": "abd1d179-7aed-46af-887b-178c42f69320",
"success": true
}Create an SMS campaign
/sms/campaignsScopesms:write
Requires sms:write, which no preset grants (an owner ticks it under "Choose permissions"). Runs the app's own create: consent, the plan's campaigns feature, content rules, the segments held up front (403 SMS_CREDIT_SHORT with how many are missing), 422 SMS_CONTENT for unsafe text and 422 SMS_NO_AUDIENCE when nobody can get it. 409 SMS_SUSPENDED while an operator has suspended SMS and 409 SMS_PAUSED while the kill switch is on. Answers 202 with a stable campaignId: the first campaign of a business (and a big one from a new business) waits for an operator (pending_review) and a key cannot approve it; otherwise it is queued and the sender, a bounded job, sends inside 9:00 to 21:00 Dhaka. Every text ends in that customer's own opt-out link. A retried Idempotency-Key never creates a second campaign or holds the balance again; the same key with other content is 409 OPERATION_KEY_CONFLICT. A test key sends nothing real: every message ends skipped with the reason "Test mode: nothing was sent", nothing is charged, and a send the live balance could not cover is refused the same way.
Send an Idempotency-Key header. Safe to retry.
Body
audienceobjectaudience.kindenumRequired- One of
all,broadcast,segment,individual,location. audience.segmentenum- One of
lapsed_7,lapsed,one_away,reward_ready,new_week,champions. audience.membershipIdsuuid[]audience.branchIduuidbodystringRequired- 1 to 1000 characters.
branchIduuidnamestringRequired- Private; customers never see it. 1 to 80 characters.
noWalletOnlybooleanprogramIduuidscheduledFordate-time
Returns 202
campaignIduuidRequiredrecipientsintegerRequiredstatusenumRequired- One of
pending_review,queued,sending,sent,canceled,rejected.
Errors
- 403
SMS_CREDIT_SHORT - 422
SMS_CONTENT - 422
SMS_NO_AUDIENCE - 409
SMS_SUSPENDED - 409
SMS_PAUSED - 409
OPERATION_KEY_CONFLICTthe same operation key arrived with different content. The key is still bound to the first request.
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ea2e5a3" \
-H "Content-Type: application/json" \
-d '{
"body": "Your 10th chai is on us this week.",
"name": "Chai card"
}'{
"data": {
"campaignId": "59aed7f3-83e8-43d6-aa83-3d0f98813f1a",
"recipients": 125,
"status": "pending_review"
},
"requestId": "108155c5-1881-48ff-b5aa-ebee4aaed3b1",
"success": true
}Read one SMS campaign and where it stands
/sms/campaigns/{id}Scopesms:read
Requires sms:read. status is pending_review, queued, sending, sent, canceled or rejected. An id of another business, another environment or outside the key's branches and cards is 404.
Path
iduuidRequired
Returns 200
bodystringRequiredbranchIdstring or nullRequiredcanceledReasonstring or nullRequiredcostPoishaintegerRequiredcountsobjectRequiredcounts.acceptedintegerRequiredcounts.deliveredintegerRequiredcounts.notDeliveredintegerRequiredcounts.queuedintegerRequiredcounts.rejectedintegerRequiredcounts.skippedintegerRequiredcounts.unknownintegerRequiredcreatedAtdate-timeRequirediduuidRequirednamestringRequiredprogramIdstring or nullRequiredrecipientsintegerRequiredreviewNotestring or nullRequiredsegmentsPerMessageintegerRequiredsendAtdate-timeRequiredstatusenumRequired- One of
pending_review,queued,sending,sent,canceled,rejected.
curl "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"body": "Your 10th chai is on us this week.",
"branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
"canceledReason": null,
"costPoisha": 40,
"counts": {
"accepted": 100,
"delivered": 118,
"notDelivered": 2,
"queued": 0,
"rejected": 1,
"skipped": 3,
"unknown": 0
},
"createdAt": "2026-11-03T08:41:12.000Z",
"id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
"name": "Chai card",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"recipients": 125,
"reviewNote": "The customer used the reward before the refund",
"segmentsPerMessage": 1,
"sendAt": "2026-11-03T08:41:12.000Z",
"status": "pending_review"
},
"requestId": "34512cb9-26e1-4239-8655-800c8c1bd5a9",
"success": true
}Read how each message of a campaign went
/sms/campaigns/{id}/messagesScopesms:read
Requires sms:read. 200 messages a page (pageSize), oldest first. A message names the customer only by customerId, with status and a plain reason for a skip or a refusal (opted out, daily limit, outside sending hours, test mode); never a name or a number.
Path
iduuidRequired
Query
pageinteger- Default
0.
Returns 200
itemsobject[]Requireditems[].customerIdstringRequireditems[].idstringRequireditems[].reasonstring or nullRequireditems[].segmentsintegerRequireditems[].sentAtdate-time or nullRequireditems[].statusenumRequired- One of
queued,skipped,accepted,unknown,delivered,not_delivered,rejected. pageSizeintegerRequired
curl "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4/messages" \
-H "Authorization: Bearer $DEVELOPER_API_KEY"{
"data": {
"items": [
{
"customerId": "eb83b9c0-5619-432d-b857-cef7fcf84edb",
"id": "d12cc02b-02fe-4a1f-83b0-5414e70fd99c",
"reason": "Customer returned the order",
"segments": 1,
"sentAt": "2026-11-03T08:41:12.000Z",
"status": "queued"
}
],
"pageSize": 50
},
"requestId": "2a2b282c-b370-4c35-8a7f-35ec851bae34",
"success": true
}Cancel an SMS campaign before it goes out
/sms/campaigns/{id}/cancelScopesms:write
Requires sms:write. Queued messages are removed and every held segment returns. 409 SMS_CAMPAIGN_CLOSED once it finished or was canceled. A retried key replays the first answer and returns nothing twice. Approving, rejecting or reconciling a campaign, the kill switch and suspension stay with operators.
Send an Idempotency-Key header. Safe to retry.
Path
iduuidRequired
Returns 200
bodystringRequiredbranchIdstring or nullRequiredcanceledReasonstring or nullRequiredcostPoishaintegerRequiredcountsobjectRequiredcounts.acceptedintegerRequiredcounts.deliveredintegerRequiredcounts.notDeliveredintegerRequiredcounts.queuedintegerRequiredcounts.rejectedintegerRequiredcounts.skippedintegerRequiredcounts.unknownintegerRequiredcreatedAtdate-timeRequirediduuidRequirednamestringRequiredprogramIdstring or nullRequiredrecipientsintegerRequiredreviewNotestring or nullRequiredsegmentsPerMessageintegerRequiredsendAtdate-timeRequiredstatusenumRequired- One of
pending_review,queued,sending,sent,canceled,rejected.
Errors
- 409
SMS_CAMPAIGN_CLOSED
curl -X POST "https://app.revisit.cards/v1/developer/sms/campaigns/e01b010d-2c65-48e1-83a0-acad79f8d3b4/cancel" \
-H "Authorization: Bearer $DEVELOPER_API_KEY" \
-H "Idempotency-Key: 5f0c2a8e-7b1d-4c3e-9a6f-0d4b8ea58e26"{
"data": {
"body": "Your 10th chai is on us this week.",
"branchId": "5eff65d7-d01c-4bd3-b26a-3d06b21fd005",
"canceledReason": null,
"costPoisha": 40,
"counts": {
"accepted": 100,
"delivered": 118,
"notDelivered": 2,
"queued": 0,
"rejected": 1,
"skipped": 3,
"unknown": 0
},
"createdAt": "2026-11-03T08:41:12.000Z",
"id": "e01b010d-2c65-48e1-83a0-acad79f8d3b4",
"name": "Chai card",
"programId": "d7595bbd-4342-41b4-9037-2e61a9f8403f",
"recipients": 125,
"reviewNote": "The customer used the reward before the refund",
"segmentsPerMessage": 1,
"sendAt": "2026-11-03T08:41:12.000Z",
"status": "pending_review"
},
"requestId": "27994b57-d58b-4d8d-b675-d9d0d1c79211",
"success": true
}