curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"from": "1FUNNEL",
"classification": "promotional",
"recipients": [
{
"to": "+2348012345678"
},
{
"to": "+2348098765432"
}
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: '1FUNNEL',
classification: 'promotional',
recipients: [{to: '+2348012345678'}, {to: '+2348098765432'}]
})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages"
payload = {
"from": "1FUNNEL",
"classification": "promotional",
"recipients": [{ "to": "+2348012345678" }, { "to": "+2348098765432" }]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"from\": \"1FUNNEL\",\n \"classification\": \"promotional\",\n \"recipients\": [\n {\n \"to\": \"+2348012345678\"\n },\n {\n \"to\": \"+2348098765432\"\n }\n ]\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb",
"business_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789ab",
"from": "1FUNNEL",
"classification": "promotional",
"status": "queued",
"settled": false,
"message": "Weekend offer for Ada.",
"template": null,
"accepted_at": "2026-10-13T09:00:00.000Z",
"intake": {
"good": 2,
"bad": 0,
"empty": 0
},
"submission": null,
"delivery_summary": null,
"warnings": []
}Submit a bulk SMS
Durably accept a bulk SMS (ADR-018 SMS-04, DEC-SMS-B01/B02). Inline
recipients only in v1. Exceeding the recipient limit rejects the entire
request with 422 / recipient_limit_exceeded before acceptance.
202 is not delivery completion.
curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"from": "1FUNNEL",
"classification": "promotional",
"recipients": [
{
"to": "+2348012345678"
},
{
"to": "+2348098765432"
}
]
}
'const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: '1FUNNEL',
classification: 'promotional',
recipients: [{to: '+2348012345678'}, {to: '+2348098765432'}]
})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages"
payload = {
"from": "1FUNNEL",
"classification": "promotional",
"recipients": [{ "to": "+2348012345678" }, { "to": "+2348098765432" }]
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/sms/bulk-messages")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"from\": \"1FUNNEL\",\n \"classification\": \"promotional\",\n \"recipients\": [\n {\n \"to\": \"+2348012345678\"\n },\n {\n \"to\": \"+2348098765432\"\n }\n ]\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-bbbbbbbbbbbb",
"business_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789ab",
"from": "1FUNNEL",
"classification": "promotional",
"status": "queued",
"settled": false,
"message": "Weekend offer for Ada.",
"template": null,
"accepted_at": "2026-10-13T09:00:00.000Z",
"intake": {
"good": 2,
"bad": 0,
"empty": 0
},
"submission": null,
"delivery_summary": null,
"warnings": []
}Authorizations
OAuth 2.0 Client Credentials for the External Integration API (ADR-015, ADR-023).
Client authentication: HTTP Basic (client_secret_basic).
Access-token audience: 1funnel-external-api.
The tokenUrl below is the production target. Sandbox uses a different
host. OpenAPI cannot bind tokenUrl to the selected servers entry; see
x-1funnel-oauth on the root document and the authentication guide when published.
Coarse OAuth scopes (if any) are token-level vocabulary (ADR-013 IA-06). They
are not the ADR-022 resource permission catalogue. Resource permissions
are enforced after token validation via application-to-business grants
(ADR-016) and are documented per operation with x-required-permissions.
These URLs are architecture targets. They are not claimed to be deployed or generally available.
Headers
Required for this designated state-changing operation (STD-API-01 D09, ADR-017 SECOP-05/06, DEC-SMS-I01 shared external idempotency model).
Scope: (application_id, business_id, operation, idempotency_key).
Permanent uniqueness within scope; seven-day response replay for equivalent
payloads; 409 idempotency_conflict on payload mismatch; reuse after
replay expiry must never create a new operation. See the optional
Idempotency-Key component description for concurrent and
failed-before-acceptance rules.
1 - 256"idem_01HZX3K9EXAMPLE"
Request correlation identifier (STD-API-01 D12). Clients may supply a value
of 1–128 characters matching A-Za-z0-9._-. Missing or invalid values are
replaced by a server-generated identifier. Distinct from W3C traceparent,
resource ids, Idempotency-Key, and webhook event ids.
1 - 128^[A-Za-z0-9._-]+$"req_01HZX3K9EXAMPLE"
Path Parameters
Business resource selector in /v1/businesses/{businessId}/… (ADR-006).
UUIDv7-compatible opaque identifier. {businessId} is not authorization
proof. The resource server must independently verify an active
application-to-business grant, required permission, and ownership (ADR-016,
ADR-022).
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
Body
Bulk SMS submission (ADR-018 SMS-04 / SMS-10, DEC-SMS-B01 / B02 / N01 / T02).
Public v1 uses an inline recipients array. File-based ingestion is not
in v1 (deferred to a future additive capability). Provide exactly one of
message or template_id.
recipients length counts all submitted entries, including duplicates
and invalid numbers. Default maximum is 1000. Exceeding the effective
limit rejects the entire request with 422 / recipient_limit_exceeded
before durable acceptance, dispatch, or billing. No partial acceptance.
Authorized sender identity for the business (from). The backend validates
that the application/business may use this sender. Arbitrary sender IDs are
not permitted. Public sender-ID management resources remain future
(ADR-018 SMS-19 / target architecture).
1 - 20"1FUNNEL"
Public message classification (lowercase snake_case). Transactional vs promotional distinction for compliance and routing policy.
transactional, promotional "transactional"
Inline recipients. maxItems reflects the default maximum (DEC-SMS-B02).
Business-specific higher quotas are planned but not available initially
and do not change this request schema.
1 - 1000 elementsShow child attributes
Show child attributes
Shared Unicode content when not using a template.
1 - 4096Active revision resolved and pinned at durable acceptance.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
Response
Durably accepted bulk message
Bulk SMS resource. Processing status is distinct from recipient delivery
outcomes (ADR-018 SMS-11–SMS-15). settled means
status == completed && retryable_failed_count == 0 when authoritative
submission counters are available — not delivery completion.
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
Authorized sender identity for the business (from). The backend validates
that the application/business may use this sender. Arbitrary sender IDs are
not permitted. Public sender-ID management resources remain future
(ADR-018 SMS-19 / target architecture).
1 - 20"1FUNNEL"
Public message classification (lowercase snake_case). Transactional vs promotional distinction for compliance and routing policy.
transactional, promotional "transactional"
Bulk-message processing lifecycle (ADR-018 SMS-13 / ADR-003 Model B).
completed means the primary processing pass finished — not all delivered
and not all retries settled.
created, scheduled, queued, processing, paused, completed, failed, cancelled "processing"
Derived when authoritative retryable_failed_count is available:
status == completed && retryable_failed_count == 0. Null when settlement
cannot be computed. Independent of delivery_summary.
RFC 3339 date-time. Response timestamps are UTC with a Z offset
(STD-API-01 D06). Client-supplied timestamps must include an explicit offset;
local times without an offset are rejected.
"2026-10-13T09:00:00.000Z"
Immutable revision pin recorded on an accepted SMS (DEC-SMS-T02 / ADR-018 SMS-08). Present on accepted message representations after the backend resolves the active revision at durable acceptance.
Show child attributes
Show child attributes
{
"template_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789ab",
"revision_id": "018f3c4e-7b2a-7c3d-8e9f-0123456789cd"
}
Intake classification counts (ADR-018 SMS-14). Omit this object when authoritative intake totals are unavailable — do not fabricate zeros.
Show child attributes
Show child attributes
Submission/processing counts (ADR-018 SMS-14). Not delivery outcomes. Omit
when unavailable — do not fabricate zeros. Do not present success as delivery.
Show child attributes
Show child attributes
Null when authoritative delivery reporting is unavailable, with a
machine-readable warnings entry. Do not fabricate zero counts. Do not
treat unavailable reporting as delivery failure.
Show child attributes
Show child attributes
{
"as_of": "2026-10-13T09:00:00.000Z",
"complete": false,
"outcomes_resolved": 850
}
Show child attributes
Show child attributes