푸시 알림 보내기 API
연결이 끝났다면 발송은 REST 한 번입니다. 이 문서는 요청 필드 전부와, 한도에 걸리기 «전에» 읽을 수 있는 숫자들을 다룹니다.
한 사람에게 보내기
to에 ext: 뒤 external_id를 주면 그 사람이 연결한 모든 기기로 갑니다. 아이폰과 안드로이드를 같이 쓰는 사람이면 두 건으로 나가고, 응답의 queued_deliveries가 그 수를 알려 줍니다. 전체에게 보낼 때는 broadcast를 씁니다.
API_KEY=<발급받은 api_key>
curl -X POST https://api.eosnoti.com/v1/messages \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"to":"ext:user_8821","kind":"notify",
"title":"결제 완료","body":"주문 #2043이 접수됐습니다",
"url":"https://app.example.com/orders/2043","priority":"high"}'
# ← 201 {"message_id":"...","queued_deliveries":2}
# queued_deliveries = 실제로 나간 기기 수 = 이번에 쓴 한도
# 구독자 전체에게 물어보기 (ask)
curl -X POST https://api.eosnoti.com/v1/messages \
-H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json" \
-d '{"to":"broadcast","kind":"ask",
"body":"회의 참석하시겠습니까?","choices":["참석","불참"]}'요청 필드
body만 필수입니다. choices와 expires_at은 ask에서만 쓰며, notify에 같이 보내면 400으로 거절됩니다.
| 필드 | 값 | 설명 |
|---|---|---|
to | ext:<id> | broadcast | 한 사람 또는 구독자 전체 |
body | 문자열 (필수) | 알림 본문 |
title | 문자열 | 알림 제목 |
kind | notify | ask | 일방 통보인지, 답을 받을 것인지 |
priority | normal | high | high는 잠금화면에 즉시 표시 |
url | 절대 주소 | 알림을 탭했을 때 열 곳 |
icon | https 주소 | 발신자 아바타(왼쪽 동그라미) |
image | https 주소 | 내용 이미지(오른쪽·카드) |
data | 임의 JSON | 앱으로 같이 실어 보낼 값 |
choices | 문자열 배열 | ask 전용 — 선택지 |
expires_at | ISO8601 | ask 전용 — 응답 마감 |
icon은 «누가 보냈는가» — 알림 왼쪽의 동그란 발신자 아바타입니다. image는 «무엇에 관한 것인가» — 오른쪽 썸네일이자 인박스 카드의 그림입니다. 둘 다 http(s) 절대 주소만 받고 메시지당 한 장입니다. 본문에 주소를 적어도 그림으로 뜨지 않습니다.
한도는 응답에 실려 옵니다
발송이 쿼터를 쓰면 201 응답에 남은 일별·월별 한도가 같이 옵니다. 같은 값이 응답 헤더에도 실립니다. 429를 만나고 나서 물러서는 대신, 이 숫자를 보고 미리 속도를 줄이십시오. remaining이 limit의 20% 아래로 내려가면 near_limit이 true가 됩니다.
# 201 응답 본문에 남은 한도가 같이 온다
{ "message_id": "...", "queued_deliveries": 2,
"quota": { "limit": 100, "used": 42, "remaining": 58,
"reset_at": "2026-07-08T15:00:00Z", "near_limit": false,
"monthly": { "limit": 1500, "used": 1212, "remaining": 288,
"reset_at": "2026-08-02T15:00:00Z", "near_limit": false } } }
# 같은 값이 헤더로도 온다
X-Quota-Limit / X-Quota-Used / X-Quota-Remaining / X-Quota-Reset (일별)
X-Quota-Month-Limit / X-Quota-Month-Used / X-Quota-Month-Remaining / … (월별)
# 일별은 매일 00:00 KST에, 월별은 가입일과 같은 날짜에 리셋된다.
# 한 건도 안 나간 발송(연결 안 된 ext:<id> 등)은 한도를 쓰지 않고 quota도 안 싣는다.한도는 API 호출 수가 아니라 «전달 건수»로 셉니다. 구독자 300명에게 보내는 broadcast 한 번은 300건입니다. 게다가 남은 한도를 넘기는 broadcast는 통째로 거절되니(일부만 나가지 않습니다), 큰 발송 전에는 remaining을 먼저 보십시오.
에러
2xx가 아닌 응답은 모두 error 객체에 code와 message를 담아 옵니다. 429가 두 종류인 것만 유의하십시오 — 잠깐 기다리면 풀리는 것과, 내일까지 기다려야 하는 것이 다릅니다.
| 코드 | 상태 | 무엇인가 |
|---|---|---|
rate_limited | 429 | 짧은 순간의 과다 호출(앱당 60초에 약 600건). 잠깐 쉬면 풀립니다 |
quota_exceeded | 429 | 요금제 한도. scope가 daily인지 monthly인지 알려 줍니다 |
plan_expired | 403 | 체험 종료 — 기다려도 풀리지 않습니다 |
plan_required | 403 | 요금제에 없는 기능 |
unauthorized | 401 | API 키가 없거나 틀렸습니다 |
invalid_target | 400 | to 형식 오류. 그 밖에 invalid_body·invalid_icon·invalid_image 등 |
# 한도를 넘긴 429 — 어느 축인지(scope)와 언제 풀리는지가 같이 온다
HTTP 429 Too Many Requests
Retry-After: <그 축이 리셋될 때까지 초>
{ "error": { "code": "quota_exceeded", "message": "daily delivery quota exceeded",
"scope": "daily", "limit": 100, "used": 100, "remaining": 0,
"reset_at": "2026-07-08T15:00:00Z" } }403 plan_expired는 체험이 끝나고 요금제를 고르지 않은 상태입니다. 기다려도 풀리지 않으므로 429처럼 재시도하시면 안 됩니다. 응답의 upgrade_url로 안내하십시오. 채널·구독자·메시지는 그대로 보관됩니다.