발송
POST /v1/batches로 대량 SMS 보내기
/v1/batches한 번의 요청으로 최대 10,000개의 번호로 보낼 수 있어요. 전체 비용이 잔액에서 한 번에 차감돼요.
요청 본문
- toarray of strings선택
공유 메시지를 받을 번호들. 'to'와 'text'를 함께 사용하거나, 'messages'를 사용하세요.
- textstring선택
공유할 텍스트예요, to와 함께 필수로 입력해야 해요.
- messagesarray of objects선택
개인화된 메시지를 위해 각 수신자마다 {to, text}를 하나씩 지정하세요.
- namestring선택
콘솔용 레이블, 최대 100자.
- referencestring선택
배치와 모든 메시지에 반환되는, 최대 100자까지 가능한 고유 ID예요.
- send_atstring선택
ISO 8601 형식의 시간, 2분 후부터 45일 후까지 지정 가능.
curl https://bulksmsapi.cc/v1/batches \
-H "Authorization: Bearer $BULKSMSAPI_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: spring-sale-0412" \
-d '{
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": [
"+14155550123",
"+447400123456",
"+4915123456789"
]
}'const res = await fetch("https://bulksmsapi.cc/v1/batches", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BULKSMSAPI_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "spring-sale-0412",
},
body: JSON.stringify({
name: "Spring sale",
text: "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
to: ["+14155550123", "+447400123456", "+4915123456789"],
}),
});
const batch = await res.json();
console.log(res.status, batch.id, batch.recipients, batch.cost);import json, os, urllib.request
req = urllib.request.Request(
"https://bulksmsapi.cc/v1/batches",
data=json.dumps({
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": ["+14155550123", "+447400123456", "+4915123456789"],
}).encode(),
headers={
"Authorization": "Bearer " + os.environ["BULKSMSAPI_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "spring-sale-0412",
"User-Agent": "northwind-app/1.0",
},
method="POST",
)
with urllib.request.urlopen(req) as res:
batch = json.load(res)
print(res.status, batch["id"], batch["recipients"], batch["cost"])<?php
$ch = curl_init("https://bulksmsapi.cc/v1/batches");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("BULKSMSAPI_KEY"),
"Content-Type: application/json",
"Idempotency-Key: spring-sale-0412",
],
CURLOPT_POSTFIELDS => json_encode([
"name" => "Spring sale",
"text" => "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to" => ["+14155550123", "+447400123456", "+4915123456789"],
]),
]);
$batch = json_decode(curl_exec($ch), true);
echo $batch["id"], " ", $batch["recipients"], " ", $batch["cost"], "\n";package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"name": "Spring sale",
"text": "Spring sale at Northwind: 20% off until Sunday. Opt out: northwind.example/sms",
"to": []string{"+14155550123", "+447400123456", "+4915123456789"},
})
req, _ := http.NewRequest("POST", "https://bulksmsapi.cc/v1/batches", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("BULKSMSAPI_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "spring-sale-0412")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var batch map[string]any
json.NewDecoder(res.Body).Decode(&batch)
fmt.Println(res.StatusCode, batch["id"], batch["recipients"], batch["cost"])
}{
"id": "bat_6TQ2W8M1ZK4N",
"object": "batch",
"name": "Spring sale",
"status": "queued",
"source": "api",
"encoding": "GSM-7",
"recipients": 3,
"max_segments": 1,
"cost": "0.02741",
"refunded": "0.00000",
"currency": "USD",
"counts": { "scheduled": 0, "queued": 3, "sent": 0, "delivered": 0, "failed": 0, "canceled": 0 },
"send_at": null,
"reference": null,
"created_at": "2026-11-07T09:31:40.102Z",
"completed_at": null,
"set_aside": []
}제외된 번호들
모든 번호는 개별적으로 정리되고 가격이 책정돼요. 발송할 수 없는 번호는 배치에서 제외되고, 요금이 부과되지 않으며, set_aside에 이유와 함께 나열돼요.
| reason | 의미 |
|---|---|
| invalid_number | 정제 후 +와 7~15자리 숫자로 읽을 수 없음. |
| duplicate | 요청에서 동일한 번호가 앞에 나타남. |
| unsupported_country | 해당 번호가 가격이 책정된 국가에 속하지 않음. |
| too_long | 개인화된 메시지가 10개 이상의 세그먼트가 필요함. |
| empty_text | 개인화된 메시지가 비어 있음. |
| suppressed | 해당 번호가 발송 제외 목록에 있음. |
남은 번호가 없으면 요청이 첫 번째 이유의 코드(예: invalid_number)로 실패하고 요금이 청구되지 않아요.
번호별 개인화된 메시지
messages를 사용해서 각 수신자에게 맞는 텍스트를 보내요: 이름, 시간, 금액 등. 각 텍스트는 개별로 계산되고 가격이 책정되기 때문에, 한 배치에 한 세그먼트와 두 세그먼트 메시지를 섞어서 보낼 수 있어요.
curl https://bulksmsapi.cc/v1/batches \
-H "Authorization: Bearer $BULKSMSAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Appointment reminders, 14 May",
"send_at": "2026-05-13T16:00:00Z",
"messages": [
{ "to": "+14155550123", "text": "Hi Ana, your dental check-up is tomorrow at 09:30." },
{ "to": "+14155550178", "text": "Hi Sam, your dental check-up is tomorrow at 11:00." }
]
}'예약 발송
send_at를 사용하면 배치와 메시지가 scheduled 상태로 대기하다가 지정된 시간에 큐로 이동해요. 요청이 수락될 때 비용이 차감되기 때문에 예약된 배치에는 항상 자금이 확보돼요. 예약된 배치는 시작하기 전까지 취소할 수 있어요.
/v1/batches/{id}/cancel예약된 배치를 취소해요: 모든 메시지가 취소되고 전체 비용이 하나의 환불 항목으로 잔액에 돌아가요. 이미 시작된 배치는 409 not_cancelable로 응답해요.
curl -X POST https://bulksmsapi.cc/v1/batches/bat_6TQ2W8M1ZK4N/cancel \
-H "Authorization: Bearer $BULKSMSAPI_KEY"배치 조회 및 목록 표시
/v1/batches/{id}실시간 상태별 카운트가 있는 배치예요. 진행 상황을 확인하려면 폴링하거나 웹훅을 사용해요.
/v1/batches계정의 배치 목록을 최신순으로 보여줘요. limit(1부터 100까지)와 starting_after(bat_ id)를 사용할 수 있어요.
배치 필드
- idstring
bat_ 뒤에 12자 문자가 붙어요.
- statusstring
예약됨, 대기 중, 전송 중, 완료됨 또는 취소됨.
- sourcestring
api 또는 console.
- encodingstring
GSM-7, 또는 텍스트에 유니코드가 필요한 경우 UCS-2를 사용해요.
- recipientsinteger
배치에 수신된 번호예요.
- max_segmentsinteger
텍스트 중 가장 큰 세그먼트 수예요.
- coststring
잔액에서 차감된 총 금액, USD.
- refundedstring
지금까지 실패하거나 취소된 메시지에 대해 환불된 총액이에요.
- countsobject
상태별 메시지 수: scheduled, queued, sent, delivered, failed, canceled
- send_atstring or null
예약된 시간(있는 경우).
- completed_atstring or null
마지막 메시지가 최종 상태에 도달했을 때.
더 큰 목록
10,000개 이상의 번호 목록은 여러 요청으로 나눠서 보내요. 각 요청에는 고유한 Idempotency-Key를 사용해요 (예: 목록 ID와 부분 번호). 메시지는 계정당 분당 최대 1,200건의 속도로 순서대로 네트워크에 전달되기 때문에, 50,000건의 캠페인도 1시간 이내에 전송이 시작돼요. 콘솔에서는 붙여넣기나 CSV 파일로 캠페인당 최대 50,000개의 번호를 처리할 수 있어요.