- Quick answer
- Developer reading path: contract to permission review
- Choose the correct Blackleaf messaging workflow
- Authenticate with a Bearer token
- Step 1: Create the message template
- Step 2: Send the accepted template
- Fields supported by the send endpoint
- Why templates should be the default
- Select a sender profile
- Create, update, retrieve, subscribe, and unsubscribe contacts
- Invoke and manage campaigns through the API
- Invoke an automation from a POS, ecommerce platform, or backend
- Send and verify two-factor authentication codes
- Retrieve messages and synchronize records
- Process the correct workspace or campaign webhook contract
- REST API and MCP serve different integration jobs
- Check the schema without treating examples as production proof
- Blackleaf API endpoint map
- How to design a production Blackleaf integration
- Error handling and troubleshooting
- Production launch checklist
- Information needed to answer an API support question
- When an API question should be escalated
- Blackleaf API frequently asked questions
- Sources and further reading
- Build messaging into your product without building carrier infrastructure
Quick answer
Create the message as a reusable template with POST /messaging/create/template. Confirm that the returned template is accepted, store its id, and send future messages with POST /messaging/send/text using that value as templateId.
Pass customer-specific values, order information, links, sender selection, and other runtime data separately. This keeps the reviewed message structure stable while allowing each send to be personalized.
The Blackleaf API is a REST API for messaging, contacts, campaigns, automations, sender profiles, 2FA, account data, and reporting. The documented operations below use the production base URL https://api.blackleaf.io, Bearer authentication and JSON responses. Their request-body schemas specify application/json; inspect the selected operation rather than copying the reference’s generic form-encoding introduction.
Use the interactive Blackleaf API documentation as the current endpoint reference. This guide explains how the pieces should be used together in a production integration.
Reference checked October 15, 2026: the public OpenAPI document identifies API version 2.8.4. The request examples below are illustrative and have not been sent. Replace sample IDs and recipients only in an authorized, permitted test setup; creating templates, changing contacts, invoking workflows and sending messages have real effects and may incur charges.
Blackleaf texting can complement supported existing POS systems without a POS switch. API access, template acceptance, recipient permission and provider acceptance are separate gates. Review the current consent guide and the actual business/use-case setup before enabling messaging.
Jump to: current webhook contracts · API versus MCP · schema and example checks.
Developer reading path: contract to permission review
Building a Texting integration? Use these four steps to prepare one application workflow for review. Keep a short record of the chosen operation, unresolved contract questions, expected events and permitted actions.
-
Read the selected operation contract
Start with the current reference and schema checks. Record the method, path, authentication, request and response fields, and any mismatch that needs confirmation. A field name in an example does not establish support for your account.
-
Trace the template and send examples
Follow template creation and acceptance through the send-by-ID example. Review the request and response handling as code; the values are illustrative. Reading an example does not authorize an API call, workflow invocation or test message.
-
Match the receiver to the webhook contract
Identify the actual workspace or campaign surface, event envelope and correlation fields. Plan verification, durable processing, duplicate handling and reconciliation without assuming one payload shape, signature or retry policy covers every receiver.
-
Review MCP scope and action permissions
Compare REST with the published workspace MCP connection. Record which reads, list changes, inactive drafts, tests and starts are allowed in the actual connection. A tool description, draft or AI plan is not an executed result or permission to send.
Finish with the production review checklist and the workflow evaluation guide. Resolve actual provider support, recipient permission and account readiness before any separately authorized pilot. Supported existing-POS Texting does not require a Blackleaf POS switch.
Choose the correct Blackleaf messaging workflow
Not every message should be implemented as a direct API send. Start by identifying the operational model.
| Use case | Recommended workflow | Primary endpoint |
|---|---|---|
| Order update, receipt, reminder, account notice, or another repeated transactional message | Create an approved template once, then send it by templateId. |
POST /messaging/create/templatePOST /messaging/send/text |
| Unique message whose complete wording cannot be known in advance | Confirm the supported dynamic-content workflow and required template context before sending; do not assume a body-only request is accepted. | POST /messaging/send/text |
| One message to a planned audience | Configure a campaign, then start, pause, or cancel it through its campaign endpoint. | POST /campaign/{_id}/send/ |
| Multi-step workflow triggered by an event | Configure an automation, then invoke it with a contact and event payload. | POST /automation/{_id}/invoke/ |
| Login or security verification | Use the dedicated 2FA send and verification endpoints. | POST /messaging/send/2faPOST /messaging/verify/2fa |
If the message has a repeatable structure, make it a template. Do not rebuild the full message body for every recipient when variables can represent the changing data.
Authenticate with a Bearer token
Every documented API operation requires an Authorization header containing a Bearer token.
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Keep the token in a server-side secret manager or protected environment variable. Do not place it in browser JavaScript, a mobile application bundle, a public repository, logs, screenshots, support tickets, or customer-visible error messages.
A safe first request is the account-balance endpoint because it verifies the production base URL and token without creating a contact or sending a message.
curl --request GET \
--url "https://api.blackleaf.io/account/balance" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Accept: application/json"
const response = await fetch("https://api.blackleaf.io/account/balance", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.BLACKLEAF_API_KEY}`,
Accept: "application/json"
}
});
const result = await response.json();
if (!response.ok) {
throw new Error(`Blackleaf request failed with HTTP ${response.status}`);
}
console.log(result.balance);
Your backend should decide which Blackleaf operations are allowed. Do not expose a generic endpoint that accepts any destination, message body, template ID, or campaign ID from an untrusted client.
Step 1: Create the message template
Create a template with POST /messaging/create/template. The request can include a message body, an optional image, and unsubscribeText.
The template body may contain supported placeholders. The API documentation uses {{firstName}} and {{url}} as examples.
curl --request POST \
--url "https://api.blackleaf.io/messaging/create/template" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"body": "Example Store: Hi {{firstName}}, your order is ready. View details: {{url}}",
"unsubscribeText": "Reply STOP to end"
}'
A successful template response includes fields such as:
{
"id": "TEMPLATE_ID",
"accepted": true,
"bodyAccepted": true,
"imageAccepted": true,
"status": "accepted",
"explanation": "Your template has been accepted."
}
Do not treat an HTTP 200 response by itself as proof that the template is ready. Check accepted, bodyAccepted, imageAccepted, status, and explanation. A template may be created while some or all of its content is not accepted for sending. The public schema currently labels its acceptance fields as strings while showing boolean examples. Confirm the observed response representation and normalize it explicitly; the string “false” must not pass a generic truthiness check.
Store the template ID
Save the returned id in your configuration or database. Associate it with a stable internal name such as order_ready_v1.
Version content changes
If the fixed wording, image, or required disclosure changes materially, create and approve a new template version before moving production traffic to it.
Keep variables narrow
Use variables for values such as a first name or destination URL. Do not place the entire message inside one unrestricted variable.
Promote only accepted templates
Treat template acceptance like a deployment gate. Production code should reference only a template ID that has completed review successfully.
The public API reference does not currently document an idempotency key for template creation. Prevent accidental duplicates by recording your own logical template name, version, content hash, returned ID, status, and creation time.
Step 2: Send the accepted template
Send an SMS or MMS with POST /messaging/send/text. For the normal production workflow, provide the recipient in to, the approved template ID in templateId, and any data needed to resolve the template placeholders.
curl --request POST \
--url "https://api.blackleaf.io/messaging/send/text" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"to": "+15551234567",
"templateId": "TEMPLATE_ID",
"contact": {
"firstName": "Jordan",
"lastName": "Lee"
},
"landingUrl": "https://example.com/orders/12345",
"sendingProfileId": "SENDER_PROFILE_ID"
}'
async function sendOrderReadyText({ phone, firstName, orderUrl }) {
const response = await fetch(
"https://api.blackleaf.io/messaging/send/text",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLACKLEAF_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify({
to: phone,
templateId: process.env.BLACKLEAF_ORDER_READY_TEMPLATE_ID,
contact: { firstName },
landingUrl: orderUrl,
sendingProfileId: process.env.BLACKLEAF_SENDER_PROFILE_ID
})
}
);
const raw = await response.text();
let result;
try {
result = raw ? JSON.parse(raw) : {};
} catch {
result = { raw };
}
if (!response.ok) {
const error = new Error(`Blackleaf send failed with HTTP ${response.status}`);
error.status = response.status;
error.details = result;
throw error;
}
return result;
}
The send response includes an id and a status. Store the message ID with the business event that caused the send, such as the order ID, customer ID, notification type, and timestamp.
The initial API response confirms that Blackleaf accepted the request for processing. Final delivery happens asynchronously. Use message-status webhooks or retrieve message records to learn whether the carrier delivered, filtered, failed, or otherwise updated the message.
Fields supported by the send endpoint
The live API reference documents the following request fields for POST /messaging/send/text.
| Field | Type | How to use it |
|---|---|---|
to |
String | The recipient phone number. Normalize and store phone numbers consistently, preferably in E.164 format. |
templateId |
String | The ID returned by the template-creation endpoint. Use this for repeatable production messages. |
body |
String | Raw message content for sends that cannot use a reusable template. Content is evaluated at send time. |
image |
String | An image reference for MMS. Prefer placing repeatable creative in the reviewed template. |
contact |
Object | Runtime customer data, such as firstName and lastName, used by supported placeholders and workflows. |
order |
Object | Runtime order context, such as order status or total, when the configured message or workflow uses it. |
location | Object | Documented store context, with example fields such as storeName, address, city, state and zip. Confirm the fields and placeholders used by the actual workflow; this object is not a jurisdiction or integration approval. |
landingUrl |
String | The destination URL supplied to a supported URL placeholder or landing-page flow. |
landingImage |
String | Optional image content for the associated landing experience. |
landingBody |
String | Optional body content for the associated landing experience. |
campaignId |
String | Optional campaign context when the send belongs to a configured campaign workflow. |
sendingProfileId |
String | Selects the configured sender profile that should route the message. |
dob |
String | Optional date-of-birth context in the format expected by the configured age-aware workflow. |
Only send fields your integration actually uses. Treat the interactive API documentation and the configuration of the specific Blackleaf account as the source of truth for supported placeholders, landing experiences, sender profiles, and age-aware behavior.
Why templates should be the default
Blackleaf evaluates message content because regulated and carrier-sensitive messaging cannot be treated as an unrestricted pipe. Templates make that review reusable.
| Consideration | Approved template | Dynamic content · confirm supported contract |
|---|---|---|
| Best use | Repeatable transactional, lifecycle, loyalty, support, and notification messages | Truly unique content that cannot be represented safely with a template |
| Review timing | Template is evaluated before production sends use its ID | Confirm the supported content-review and template context before using this workflow |
| Consistency | Fixed structure with controlled runtime values | Complete body can change on every request |
| Operational risk | Lower risk of an unexpected content change reaching production | Requires stronger application-side validation and monitoring |
| Recommended production pattern | Default | Confirm before use; not demonstrated here |
The current send reference lists body and templateId, and marks templateId required in its request documentation. Do not infer that a body-only request is supported from the presence of a body field. Confirm the actual dynamic-content contract for your account; this guide’s send examples use an accepted template ID.
Select a sender profile
A sender profile represents the Blackleaf sending configuration used to route a message. Retrieve available profiles with GET /messaging/senders/profiles.
curl --request GET \
--url "https://api.blackleaf.io/messaging/senders/profiles?pageSize=100&page=0" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Accept: application/json"
The response is paginated and contains an items array. Each documented profile includes fields such as _id, title, healthy, sticky, dateCreated, and dateModified.
Store the selected profile’s _id and pass it as sendingProfileId when the application must choose a specific profile. Do not hard-code an ID copied from another Blackleaf organization or environment.
Create, update, retrieve, subscribe, and unsubscribe contacts
Blackleaf provides contact endpoints for synchronizing customer identity, profile, subscription status, and rewards data.
| Operation | Endpoint | Purpose |
|---|---|---|
| List contacts | GET /contact/ |
Retrieve contacts with filters and pagination. |
| Get one contact | GET /contact/{phone} |
Retrieve a contact by phone number. |
| Create or update | POST /contact/set |
Upsert a contact using phone, email, customer ID, or other supplied identity data. |
| Subscribe | POST /contact/{phone}/subscribe |
Set the Blackleaf contact to a subscribed state. |
| Unsubscribe | POST /contact/{phone}/unsubscribe |
Suppress future messaging to the contact. |
| Adjust rewards | POST /contact/{phone}/points |
Add or remove points with a title and description. |
Upsert a contact
curl --request POST \
--url "https://api.blackleaf.io/contact/set" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"customerId": "customer_12345",
"phone": "+15551234567",
"email": "jordan@example.com",
"firstName": "Jordan",
"lastName": "Lee",
"dob": "1990-05-20T00:00:00.000",
"consumerType": "AdultUse"
}'
The contact upsert endpoint can identify and update a contact by phone number, email, or customer ID. Use a stable customerId from the source system whenever one exists, and normalize phone numbers before synchronization.
Only call the subscribe endpoint after the business has a valid basis to mark the person subscribed for the intended message type. Retain the opt-in disclosure, source, timestamp, customer action, and other evidence required by the business’s compliance process. Registration and API access do not turn every phone number in a POS or CRM into an eligible marketing recipient.
When a customer opts out, process the change immediately and synchronize it back to every system that can initiate a send. Review Blackleaf’s SMS opt-in and consent guide and dispensary SMS compliance framework before launching production marketing traffic.
Invoke and manage campaigns through the API
Use a campaign when Blackleaf should manage one message across a defined audience. The API supports listing, creating or modifying, starting, pausing, and canceling campaigns.
| Operation | Endpoint |
|---|---|
| List campaigns | GET /campaign/ |
| Create or modify a campaign | POST /campaign/set |
| Add contacts | POST /campaign/contact/add |
| Remove contacts | POST /campaign/contact/remove |
| Start sending | POST /campaign/{_id}/send/ |
| Pause | POST /campaign/{_id}/pause/ |
| Cancel or delete | POST /campaign/{_id}/cancel/ |
For most integrations, the safest path is to configure and review the campaign in Blackleaf, retain its campaign ID, and invoke the resulting endpoint from the external system.
curl --request POST \
--url "https://api.blackleaf.io/campaign/CAMPAIGN_ID/send/" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Accept: application/json"
The documented response contains a status such as Started. Starting a campaign is an operational action, so protect the endpoint with application authorization, prevent duplicate operator actions, log the initiating user or event, and verify the campaign’s audience and state before invoking it.
If the application needs to build campaigns entirely through the API, follow the current campaign schema in the interactive documentation. Campaign configuration contains significantly more state than a single-message send and should not be reduced to a guessed minimal payload.
Invoke an automation from a POS, ecommerce platform, or backend
Use POST /automation/{_id}/invoke/ when an external event should enter a workflow already configured in Blackleaf. The request contains a contact object and an optional payload object.
curl --request POST \
--url "https://api.blackleaf.io/automation/AUTOMATION_ID/invoke/" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"contact": {
"customerId": "customer_12345",
"phone": "+15551234567",
"firstName": "Jordan",
"lastName": "Lee"
},
"payload": {
"eventId": "order_98765_ready",
"orderId": "98765",
"status": "Ready for Pickup"
}
}'
The documented success response contains {"status":"Started"}.
Include a stable event identifier in the payload even when the current workflow does not display it. Your application can use that value to prevent the same source event from invoking the automation twice and to trace a customer question back to the originating system.
The source application should decide that a real event occurred. Blackleaf should manage the configured messaging workflow that follows. Do not trigger an automation merely because a page was refreshed or a webhook was delivered more than once.
Send and verify two-factor authentication codes
Blackleaf provides dedicated endpoints for one-time security codes. Do not build 2FA by generating a code and inserting it into a marketing-message template.
Send a code
curl --request POST \
--url "https://api.blackleaf.io/messaging/send/2fa" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"phone": "+15551234567",
"digits": 6,
"expires": 300
}'
Verify a submitted code
curl --request POST \
--url "https://api.blackleaf.io/messaging/verify/2fa" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"phone": "+15551234567",
"code": 123456
}'
The send endpoint documents phone, digits, and expires. The verification endpoint requires the phone number and submitted code. Apply application-level controls for repeated requests, failed attempts, session binding, and abuse prevention.
Retrieve messages and synchronize records
Use GET /messaging/messages to retrieve message records. The endpoint supports:
campaignIdto filter messages associated with a campaignpageSizefrom 1 to 1,000page, beginning with page 0lastModifiedas a UTC ISO 8601 timestampsortasascordesc
curl --request GET \
--url "https://api.blackleaf.io/messaging/messages?pageSize=100&page=0&sort=asc&lastModified=2026-08-01T00%3A00%3A00Z" \
--header "Authorization: Bearer $BLACKLEAF_API_KEY" \
--header "Accept: application/json"
Paginated list responses contain items, page, pageSize, totalItems, and hasMoreItems. Continue requesting the next page until hasMoreItems is false.
A message record may contain the message ID, destination, source number, template ID, body, image, campaign context, sender profile, carrier, delivery status, message type, number of parts, cost, and created or modified timestamps.
When synchronizing by lastModified, save the newest successfully processed timestamp only after the complete page or batch is committed. Re-read a small overlap window and upsert by Blackleaf message ID so records modified near a page boundary are not lost.
Process the correct workspace or campaign webhook contract
The current public webhook reference distinguishes workspace Message Status, workspace Inbound Message and campaign event URLs. These are different payloads. Keep the configured receiver surface in the event record before interpreting a field; not every inbound object has the same envelope.
| Receiver | Route and correlate using | Important boundary |
|---|---|---|
| Workspace Message Status | messageId, status, dateModifiedLong | Unix-millisecond update time; errorData is optional and may contain errorId and description. |
| Workspace Inbound Message | type, messageId, from, to, org, dateCreated | Flat JSON. type is message, unsubscribe or subscribe. The messageId identifies the reply, not the original outgoing message. |
| Campaign Message Status | messageId, status, date | Campaign event time is an ISO UTC string; it is not the workspace dateModifiedLong field. |
| Campaign Inbound Message | contact.phone, payload.message, payload.messageId, payload.date | Nested campaign envelope, including subscription replies. Do not apply the flat workspace parser. |
| Campaign Opt Out / Landing Viewed | phone and date for opt-out; contact.phone and payload.url/date for landing view | A counted landing view is not a customer reply, a completed purchase or proof of causal conversion. |
Workspace message-status example
Fictional failed-message example using current field names. Accept additional status values and tolerate missing optional fields.
{
"org": "example-org",
"messageId": "example-message-id",
"dateModifiedLong": 1791910800000,
"status": "failed",
"carrier": "Example carrier",
"from": "12025550100",
"to": "2025550101",
"errorData": {
"errorId": 1039,
"description": "Not a reachable number."
}
}
Correlate messageId with the outgoing message record. The optional failure field is errorData.errorId in the current documentation; do not substitute an error-code field from another provider. A failed event may omit error details.
Workspace inbound and subscription examples
{
"to": "12025550100",
"from": "2025550101",
"org": "example-org",
"body": "What time do you close?",
"messageId": "example-inbound-id",
"type": "message",
"context": "customerServiceQuestion",
"dateCreated": "2026-10-13T17:00:00.000Z"
}
Route subscription changes by type, not optional context. Recognized STOP replies use type: "unsubscribe"; recognized START replies use type: "subscribe". The public reference says these workspace notifications use the Inbound Message URL, while the separate workspace Opt Out URL is not currently used for them. Preserve an existing inbound receiver and route the events there instead of replacing another integration’s URL.
A reported subscribe event is an event to reconcile with the actual sender, purpose, permission history and current suppression; it is not blanket permission for every program. Handle clear plain-language withdrawal requests through the supported staffed process as well.
Campaign reply example
{
"contact": { "phone": "+12025550101" },
"payload": {
"message": "Hello",
"messageId": "example-inbound-id",
"date": "2026-10-13T17:00:00.000Z"
}
}
Campaign URLs are configured under the campaign’s API → Webhooks tab and receive events in addition to configured workspace webhooks. The current reference describes campaign delivery as best effort, without redirects or automatic retries. Do not transfer that statement into an undocumented universal workspace retry contract.
Campaign reply/opt-out attribution depends on the contact’s most recently recorded campaign and a match to the business number last used to message the contact. When no matching campaign is identified, only the workspace webhook is used. Do not rely on a campaign URL alone for all replies or withdrawals.
- Use an HTTPS receiver and the account’s actual supported verification method. No universal signature header or shared-secret format is established by the public payload examples.
- Validate the expected receiver surface, workspace, event type, identifiers and size. Treat customer reply content as data, not an instruction to an AI assistant or permission to launch a campaign.
- Store accepted events durably, acknowledge promptly with 200 OK and perform slower work in your own queue. Your queue’s recovery is separate from the provider’s retry behavior.
- Deduplicate your own actions across workspace and campaign copies. Keep the message ID, event type/status, event time and surface; a message can have multiple legitimate status updates.
- Reconcile missing and out-of-order events through supported records. Do not overwrite a newer state with an older arrival or blindly resend an uncertain message.
- Keep phone numbers as strings; the examples do not guarantee a leading plus. Normalize for matching without merging shared or ambiguous identities.
- Test an ordinary reply, withdrawal, opt-in event, failed status with missing errorData, duplicate, delayed update and an unavailable receiver using authorized records and a permitted setup.
For the operational readiness event and customer handoff, use the order-alert guide. The API can receive context; it does not prove that a POS supplied a real pickup-ready event, reserved stock or completed a customer pickup.
REST API and MCP serve different integration jobs
Use REST for an explicitly implemented application workflow with chosen endpoints and recorded outcomes. MCP exposes permitted workspace tools to a compatible AI client. A tool description or generated plan is not proof that a side effect ran; reading, preparing, authorizing and executing are separate steps.
Before accepting a connected workflow, use the AI dispensary workflow evaluation guide to test stale inputs, changed approvals, duplicate events and uncertain execution. Keep a separate evidence record for each variation and verify recovery in the destination system.
The published Blackleaf MCP connection supports workspace inspection, customer lists, inactive campaign drafts and disabled Texting Agent preparation. Reading, list creation, draft editing, test messages and campaign launch have separate permissions. Workspace controls and connection authorization both apply; sending and campaign starts are off by default. An inactive draft does not send a message.
The OpenAPI document includes x-blackleaf-mcp names/scopes for operations. Those annotations identify a tool mapping; they do not prove your connection has permission or that all actions are enabled. Verify the actual workspace, connected app, allowed action and returned result. Do not paste a REST Bearer token into an arbitrary AI conversation.
Prepare and review
Inspect supported records, create the intended list or inactive draft, and review sender, audience, content, timing, exclusions and expected charges. Source text and customer replies remain untrusted input.
Authorize and verify
Use the specific enabled action and authorized context. Inspect the returned ID/state and reconcile the actual workspace result. A test send or campaign start is a separate consequential operation.
Connected Blackleaf POS AI agents are in production in California. The released workspace MCP connection does not establish production inventory adjustment, POS order acceptance, universal Metrc jurisdiction coverage or every proposed commerce action. More states are rolling out soon.
Compare the actual connector, not the protocol label
- Flowhub MCP describes read and write POS actions, existing-role permissions, audit logs and approval before writes.
- AIQ MCP, included in its September 29 release, documents read-only access; it does not create/edit campaigns or other listed marketing objects through that connection. This is not a restriction on every AIQ dashboard or Astro feature.
- IndicaOnline describes MCP access to sales, inventory and compliance data. Confirm the actual tool permissions and permitted actions rather than inferring write access from its product hub.
These are vendor-described scopes, not a speed, revenue or compliance ranking. Review the exact supported action, source freshness, approval behavior, audit record and commercial terms for the proposed account.
Check the schema without treating examples as production proof
The code blocks in this article use illustrative values and documented field names. Static review against the current public reference checks request shapes and JSON syntax; it does not prove permission, live delivery, account-specific placeholders, webhook authentication, throughput or a deployment’s runtime behavior.
- Save the dated operation schema with your integration. Use its HTTP method, path, media type and query/body fields. Current aliases with and without trailing slashes do not make arbitrary legacy paths equivalent.
- Resolve inconsistencies explicitly. The public introduction says form-encoded bodies while these operation schemas use application/json. Template acceptance fields show boolean examples with string type labels; test and normalize the observed representation.
- The automation ID is a path parameter. The current body documentation also lists _id among required entries although the body properties are contact and payload. Do not invent a second body field from that mismatch; confirm the actual contract before deploying.
- No client idempotency key is documented for template creation or direct send. A payload eventId is your correlation data unless the supported workflow expressly uses it; it is not a server-side deduplication guarantee.
- Keep sent, accepted, delivered, read, order-ready and collected evidence separate. A successful HTTP response or generated MCP plan cannot stand in for the next state.
Before an authorized pilot, confirm required fields, actual response types, error shape, sender/recipient scope and recovery behavior. Do not create contacts, alter rewards, invoke workflows or send test traffic merely to read this guide.
Blackleaf API endpoint map
The current API documentation groups operations into messaging, automations, campaigns, contacts, products, orders, account data, and analytics.
| Area | Endpoints | Typical integration use |
|---|---|---|
| Messaging | /messaging/create/template/messaging/send/text/messaging/send/2fa/messaging/verify/2fa/messaging/messages/messaging/senders/profiles |
Create reviewed content, send SMS or MMS, handle security codes, retrieve message history, and select sender profiles. |
| Automations | /automation/{_id}/invoke/ |
Trigger a configured workflow from a POS, ecommerce event, CRM, data warehouse, or application backend. |
| Campaigns | /campaign//campaign/set/campaign/contact/add/campaign/contact/remove/campaign/{_id}/send//campaign/{_id}/pause//campaign/{_id}/cancel/ |
Create, inspect, populate, start, pause, and cancel audience-based sends. |
| Contacts | /contact//contact/{phone}/contact/set/contact/{phone}/subscribe/contact/{phone}/unsubscribe/contact/{phone}/points |
Synchronize customer profiles, subscription state, and rewards activity. |
| Products and orders | /product//product/{id}/order//order/{id} |
Retrieve commerce data available to the account. |
| Account | /account/balance |
Retrieve organization, balance, and documented automatic-funding configuration fields. |
| Analytics | /analytics/summary//analytics/messaging//analytics/orders//analytics/inventory/report-card/ |
Retrieve account, messaging, order, and inventory-related reporting available to the account. |
Use the exact path shown in the live API reference. Several endpoints intentionally include a trailing slash. Do not assume that a different path, method, or legacy query-string version is equivalent.
How to design a production Blackleaf integration
1. Separate configuration from runtime data
Store API tokens, template IDs, automation IDs, campaign IDs, and sender profile IDs as protected configuration. Pass customer, order, link, and event values at runtime.
2. Create an application-side message key
Before calling Blackleaf, assign the business event a stable key such as order_98765_ready_v1. Record the event key, recipient, template ID, request time, Blackleaf message ID, initial status, and final delivery state.
3. Prevent duplicate sends before making the request
The public API reference does not document a client-supplied idempotency header for message sends. Enforce uniqueness in your own database before sending. A unique constraint on the source event and notification type is stronger than an in-memory flag.
4. Do not blindly retry an uncertain send
If the connection times out after the request leaves your server, the message may already have been accepted. A blind retry can produce a duplicate. Mark the attempt as uncertain, reconcile against message records where possible, and use a controlled recovery process.
5. Treat delivery as asynchronous state
Model message state separately from the HTTP request. Store the initial response, update the record from webhooks, and reconcile through GET /messaging/messages.
6. Normalize identity consistently
Use one phone-number normalization strategy across contacts, sends, orders, webhook matching, suppression, and customer support. Prefer E.164 storage, and do not create a second customer record merely because punctuation differs.
7. Keep suppression in the critical path
Before initiating a marketing send, verify that the customer is eligible for the specific use case. When an opt-out occurs, update Blackleaf and every external system that can trigger a message.
8. Monitor by template, sender, carrier, and workflow
Aggregate accepted, delivered, failed, filtered, and inbound events by template ID, sender profile, carrier, campaign, automation, and source event. A single account-wide delivery rate can hide a failing integration path.
Error handling and troubleshooting
Blackleaf uses standard HTTP response codes, but integration logic should preserve the response body because it may contain the most useful explanation.
| Observed problem | What to check | Recommended response |
|---|---|---|
| Authentication request fails | Production base URL, Bearer prefix, token value, account permissions, accidental whitespace, and whether the token was exposed or rotated |
Correct or rotate the credential. Do not retry repeatedly with the same rejected token. |
| Template is returned but not usable | accepted, bodyAccepted, imageAccepted, status, and explanation |
Correct the rejected content and create a new reviewed template version. |
| Send request is accepted but the customer did not receive it | Message-status webhook, message record, carrier, destination format, suppression, sender health, filtering information, and final delivery status | Do not assume the initial API response means delivery. Investigate the asynchronous message state. |
| Template variable is blank | Placeholder spelling, whether the correct runtime object was supplied, null values, and template version | Validate required personalization fields before sending and reject incomplete application events. |
| Wrong sending configuration is used | The sendingProfileId, account environment, and IDs stored in configuration |
Retrieve sender profiles from the same account and environment, then update the mapping. |
| Duplicate texts are sent | Automatic retries, repeated source events, webhook-trigger loops, concurrent workers, and missing database uniqueness | Add application-side idempotency before the Blackleaf call. Do not rely on process memory. |
| Inbound replies are missing | Webhook URL, HTTPS availability, response codes, queue failures, event deduplication, and the account’s webhook configuration | Acknowledge valid events quickly and move downstream work to a durable queue. |
| List synchronization misses or repeats records | Zero-based page number, hasMoreItems, UTC timestamp format, sort direction, and the saved modified cursor |
Upsert by Blackleaf object ID and overlap the incremental-sync window. |
When asking Blackleaf support to investigate, provide the organization, endpoint, approximate UTC time, Blackleaf message or object ID, HTTP status, sanitized response body, and the expected result. Do not send the API token or unnecessary customer data.
Production launch checklist
- The integration uses
https://api.blackleaf.iofor production. - The API token is stored server-side and excluded from logs and source control.
- Each repeatable message has an accepted template and a versioned internal mapping.
- Production sends use
templateIdinstead of rebuilding the body for every customer. - Required template variables are validated before the request is sent.
- Phone numbers are normalized consistently.
- The correct sender profile is selected for the account and workflow.
- Consent, customer eligibility, and opt-out suppression are enforced before marketing sends.
- Source events have application-side idempotency.
- The initial send response and Blackleaf message ID are persisted.
- Webhook endpoints acknowledge valid events quickly and process them idempotently.
- A polling or reconciliation job checks for events that a webhook handler may have missed.
- Retries distinguish a request that never left the server from a request with an uncertain outcome.
- Rate limits and expected throughput have been confirmed for the account and use case.
- Alerts exist for authentication failures, rejected templates, delivery degradation, queue failures, and repeated webhook errors.
- Logs contain identifiers and timestamps without exposing tokens or unnecessary customer content.
Information needed to answer an API support question
Most integration questions can be answered quickly when the request includes the following details:
- The exact endpoint and HTTP method
- Whether the request uses production, test, or development
- The approximate request time in UTC
- The HTTP response status
- The sanitized response body
- The Blackleaf template, message, campaign, automation, or contact ID involved
- The expected behavior and the actual behavior
- Whether the issue happens consistently or only for certain recipients, carriers, templates, or sender profiles
- Whether a message-status or inbound webhook was received
- Whether retry logic may have run
Replace the API token and sensitive customer content before sharing an example. A useful sanitized request preserves field names, data types, endpoint paths, IDs relevant to the investigation, timestamps, and response details.
When an API question should be escalated
The documentation can answer how to authenticate, which endpoint to call, why templates are preferred, which fields are documented, how pagination works, and how delivery or inbound events are reported. Some questions depend on account configuration or platform-side data and should not be answered by guessing.
| Question | Can the guide answer it? | Next step |
|---|---|---|
| Which authentication header should I send? | Yes | Use Authorization: Bearer YOUR_API_KEY. |
| Should I create a template before sending? | Yes | Use a template for every repeatable message structure. |
| Why does a successful API response not prove delivery? | Yes | Explain asynchronous carrier delivery and check the message status. |
| Which sending profile is enabled for this customer’s account? | No, not from the article alone | Retrieve the account’s sender profiles or inspect its Blackleaf configuration. |
| Why was this specific template rejected? | Sometimes | Start with the response’s acceptance fields and explanation. Escalate if the explanation is absent or unclear. |
| Why did one specific carrier filter a message? | No, not conclusively | Provide the message ID, carrier, UTC time, final status, error data, template ID, and sender profile for investigation. |
| What webhook signature or secret does this account use? | No | Use the verification method configured for the account or confirm it with Blackleaf. |
| What is this account’s exact rate limit or message throughput? | No | Confirm the account plan, campaign type, route, and expected production volume with Blackleaf. |
| Is an undocumented request field or placeholder supported? | No | Do not infer support from a similar field. Check the live schema or confirm with Blackleaf. |
Answer from the documented endpoint, observed response, and account configuration. If a conclusion requires information that is not present, ask for the missing details or escalate instead of inventing platform behavior.
Blackleaf API frequently asked questions
What is the fastest correct way to send a text with the Blackleaf API?
Create the repeatable message with POST /messaging/create/template, confirm it is accepted, save the returned template ID, and call POST /messaging/send/text with the recipient, templateId, and any runtime data.
Can I omit templateId when sending dynamic content?
Do not assume that. The current send documentation lists a body field but marks templateId required in its request documentation. Confirm the supported dynamic-content contract for your account. The examples in this guide use accepted template IDs and do not demonstrate a body-only send.
Does a successful send response mean the text was delivered?
No. It means the request was accepted for processing. Delivery is asynchronous. Use the returned message ID, message-status webhooks, and GET /messaging/messages to track the final state.
How do I personalize an approved template?
Put supported placeholders in the template and pass the corresponding runtime values through fields such as contact, order, and landingUrl. Validate every required value before sending.
How do I send an MMS?
The template-creation and send endpoints support an image field. Include the image in the reviewed template when it is repeatable, confirm imageAccepted, and use the resulting template ID for production sends.
How do I choose which number sends the message?
Retrieve the account’s sender profiles with GET /messaging/senders/profiles and pass the selected profile’s ID as sendingProfileId. The profile controls the configured routing rather than the application supplying an arbitrary source phone number.
Should I call the Blackleaf API directly from a browser?
No. Make authenticated Blackleaf API calls from a trusted backend so the Bearer token is not exposed to the user or embedded in public client code.
What is the difference between a direct send, campaign, and automation?
A direct send delivers one message request to one recipient. A campaign manages a planned message across an audience. An automation starts a configured workflow from an event and can contain timing or additional steps.
How should an order-ready integration be built?
Create and approve an order-ready template, map it to an internal version, listen for the real order-ready event, enforce a unique event key, verify customer eligibility, call the send endpoint with the template ID and order data, store the returned message ID, and update delivery state from webhooks.
How do I stop a contact from receiving messages?
Call POST /contact/{phone}/unsubscribe and synchronize the opt-out to every external system that can initiate a send. Do not wait for a scheduled batch before suppressing future marketing messages.
Does calling the subscribe endpoint prove customer consent?
No. The endpoint updates contact state in Blackleaf. The business must separately retain evidence showing how and when the customer agreed to receive the intended type of messages.
How do I avoid duplicate messages?
Assign each source event a stable application-side key and enforce uniqueness in persistent storage before calling Blackleaf. Also deduplicate source webhooks, automation triggers, and retry jobs.
What should I do after a send request times out?
Do not immediately send the same request again. The first request may already have been accepted. Record the outcome as uncertain, reconcile message activity where possible, and use a controlled recovery process to avoid duplicates.
What are the API rate limits?
Rate limits and message throughput depend on the account plan and campaign type. Confirm the expected request rate, sending volume, and delivery requirements with Blackleaf before load testing or launching high-volume production traffic.
Where can I see the current endpoint schemas?
Use the interactive documentation at api.blackleaf.io. It contains the current methods, paths, request fields, examples, and response schemas.
Which webhook receives workspace opt-out replies?
The current public reference routes recognized unsubscribe and subscribe events to the workspace Inbound Message URL, distinguished by type. It says the separate workspace Opt Out URL is not currently used for those notifications. Campaign opt-out URLs are a separate contract and attribution scope.
Does an inbound reply use type inbound or message?
The current flat workspace example uses type message for an ordinary reply, with unsubscribe and subscribe for recognized subscription changes. Context is optional. A campaign reply uses the nested contact/payload envelope instead; do not apply one parser to every receiver.
Are campaign webhooks retried automatically?
The current campaign reference describes best-effort delivery without redirects or automatic retries. Build your own durable receiver processing and reconciliation. Do not infer a universal workspace retry policy from campaign documentation or assume a provider will recover a missed event.
Does Blackleaf MCP automatically launch a campaign?
No. Inactive draft creation and sending are separate actions. The published MCP connection uses workspace controls and connection authorization, with sending and campaign starts off by default. Verify the enabled tool, authorized context and actual result; a saved draft or AI plan is not a launch.
Sources and further reading
- Blackleaf API documentation
- Current public OpenAPI document and webhook contracts
- Blackleaf MCP release and permission boundaries
- Blackleaf dispensary SMS integrations
- POS-integrated vs. POS-agnostic dispensary text messaging
- Cannabis SMS compliance for dispensaries
- Opt-in and consent for dispensary SMS
- 10DLC registration for dispensaries
- API access for campaigns and automations
Build messaging into your product without building carrier infrastructure
Blackleaf gives POS platforms, ecommerce systems, CRMs, data teams, and application developers one API for approved message templates, SMS and MMS, contacts, campaigns, automations, 2FA, delivery events, and customer replies.
Explore the Blackleaf API documentation or contact Blackleaf to review your integration.