자버 자유 발송 API
자유 발송 API는 미리 만든 세트 없이 보낼 원문을 보내면 자버가 광고·정보성과 채널을 정해 알림톡·이메일·문자 캠페인으로 바꾸고, 버튼 링크와 받는 사람을 붙여 테스트 발송과 발송·예약까지 하는 HTTP API입니다. 자버 MCP 서버와 같은 기능을 서버에서 REST 로 부르는 방법이며, 툴 하나가 엔드포인트 하나에 대응합니다.
카카오 채널을 연동한 스페이스는 자기 채널로 보내고, 연동하지 않은 스페이스는 자버 공용 채널로 웹 발송과 같은 한도 안에서 보냅니다(채널 미연동 발송과 한도). 이메일로도 보낼 수 있고(이메일로 보내기), 정보성 안내는 문자로도 보낼 수 있습니다(문자로 보내기).
처음이면
자버 계정이 없으면 사람이 먼저 직접 가입해야 합니다. AI 도구가 계정을 대신 만들 수는 없습니다.
| 먼저 알아 둘 것 | 내용 |
|---|---|
| 가입 보너스 | 가입하면 500원이 들어오고, 건당 약 50원이라 약 10건을 보낼 수 있습니다 |
| 카카오 채널 연동 전 | 이메일과 알림톡을 합쳐 체험 10건까지입니다(알림톡의 내 링크·광고 쿠폰 안내는 받는 사람 스페이스 누적 3명까지) |
| 충전해도 | 카카오 채널을 연동하기 전에는 10건을 넘길 수 없습니다. 10건을 넘겨 보내려면 카카오 채널 연동과 충전이 모두 필요합니다 |
| 보낼 인원 확인 | get_send_readiness 에 targets(보낼 인원)를 넣으면 channels[].capacity 에 지금 보낼 수 있는 수와 모자란 수, 해제 방법이 나옵니다 |
| 문자 | 정보성 안내만, 건당 50원입니다. 자버 전자문서 설정에서 문자 발송을 먼저 켜야 합니다(문자로 보내기) |
AI 도구에 따라 첫 연결의 401 응답 본문(가입 안내)을 모델에게 넘기지 않습니다. Claude Code 비대화형 실행에서 본문이 전달되지 않는 것을 확인했습니다. 그래서 처음이면 AI 도구가 시작 안내(인증 없음)를 먼저 읽거나, 연결할 때 열리는 OAuth 동의 화면에서 바로 가입하도록 안내하세요. 동의 화면은 로그인 전이면 가입 안내를 보여 주고, 가입한 뒤 같은 화면으로 돌아와 허용할 수 있습니다.
- 가입: https://jober.io/start/ai?src=mcp&client=docs 에서 이메일·휴대전화·카카오·네이버·구글로 가입합니다. 가입비는 없고 가입 보너스 500원(약 10건)이 들어옵니다.
- 연결: 가입 직후 화면에서 AI 도구 연결 방법을 고릅니다. 원격 MCP 는 AI 도구에서 자버를 다시 연결하면 열리는 동의 화면에서 로그인하고 허용하고, 로컬 패키지는 개인 API 키를 만들어
JOBER_API_KEY에 넣습니다. 로그인 전에 동의 화면이 열려도 그 화면에서 가입한 뒤 돌아와 허용할 수 있습니다(연결 요청은 30분 동안 유효). - 준비 확인: 연결되면
get_send_readiness(REST 는GET /readiness)를 먼저 부릅니다.onboarding.nextStep.message를 사용자에게 그대로 안내하고,channels로 채널마다 지금 보낼 수 있는지 봅니다. - 첫 발송: 이메일은 가입 후 바로 보낼 수 있습니다. 다만 카카오 채널을 연동하기 전에는 이메일과 알림톡을 합쳐 체험 10건까지이고, 충전해도 늘지 않습니다. 카카오 알림톡은 연동 전에 자버 공용 채널로 보냅니다(내 링크·광고 쿠폰 안내는 받는 사람 스페이스 누적 3명까지). 문자는 정보성 안내만 보낼 수 있고 문자 발송을 먼저 켜야 합니다. 실제로 보내기 전에
POST /campaigns/{uuid}/test-send로 본인 번호나 메일에 테스트로 보내 보세요(스페이스 하루 5번).
예를 들어 예약 시스템이 지난 방문 고객에게 쿠폰·적립금 소멸 안내를 보내고, 이벤트 내용은 버튼 뒤 내 페이지로 보여 줄 때 씁니다.
어떤 API 를 쓸까요
| 구분 | 자유 발송 API(이 문서) | 발송 API |
|---|---|---|
| 보내는 내용 | 문자 원문을 서버가 알림톡·이메일 캠페인으로 변환 | 웹에서 미리 만든 빠른 발송 세트 |
| 버튼 링크 | 내 링크 붙이기(메시지+링크), AI 안내 페이지·설문 생성, 기존 설문 | 세트에 정한 문서·링크 |
| 받는 사람 | 명단 표(행·열)를 확인한 뒤 연락처에 저장하고 발송 대상으로 지정 | 요청당 최대 100명 |
| 주소 | https://api.jober.io/openApi/v2/agent | https://api.jober.io/openapi/v1 |
| 인증 | Authorization: Bearer 회사 API 키(jbr_co_), 개인 API 키(jbr_live_) 또는 OAuth 토큰 | x-jober-openapi 회사 API 키 |
| 채널 | 카카오 알림톡(채널 연동 권장. 미연동이면 자버 공용 채널, 체험 한도와 링크·광고 3명 상한), 이메일(가입 후 바로) | 세트에 정한 채널 |
준비물
- 자버 스페이스와 그 스페이스의 관리자 계정(없으면 가입)
- 회사 API 키, 개인 API 키 또는 OAuth 토큰(아래 인증)
- 카카오 채널 연동(권장, 필수 아님). 연동하지 않아도 보낼 수 있지만 자버 공용 채널로 나가고 체험 한도와 링크·광고 3명 상한이 걸립니다(채널 미연동 발송과 한도). 연동: https://jober.io/kakao-channel?connect=1(연동 창이 바로 열림)
인증
모든 요청에 Authorization 헤더를 보냅니다. 발송 API 의 x-jober-openapi 키는 받지 않습니다.
Authorization: Bearer jbr_live_xxxxxxxx| 방식 | 발급 | 비고 |
|---|---|---|
회사 API 키(jbr_co_) | 설정 > AI 도구 연동의 회사 API 키에서 이름과 권한을 골라 발급(스페이스당 활성 5개) | 사내 서버 연동용. AI 에이전트 권한(아래 5종)을 골라 발급한 키만 이 API 에 쓸 수 있습니다. 호출은 키를 만든 관리자 이름으로 처리·기록됩니다. MCP 에는 쓸 수 없습니다 |
개인 API 키(jbr_live_) | 같은 화면의 개인 API 키에서 이름과 권한을 골라 발급(사람당 스페이스마다 20개) | MCP 와 같은 키를 그대로 씁니다 |
OAuth 2.1 토큰(jbr_at_) | MCP 와 같은 인가 서버. /oauth/authorize·/oauth/token 요청에 resource=https://api.jober.io/openApi/v2/agent 를 넣습니다 | 동의 화면에 'API 연동'으로 표시됩니다. 보호 리소스 메타데이터: https://api.jober.io/.well-known/oauth-protected-resource/openApi/v2/agent |
- OAuth 토큰 하나는 사용 대상(resource) 하나에서만 쓸 수 있고, MCP 토큰과 이 API 의 토큰은 서로 호환되지 않습니다.
resource를 빼고 받으면 MCP(https://api.jober.io/mcp)용 토큰이 나오고, 그 토큰을 이 API 에 보내면 401J_41_25_3_6(data.reason: "RESOURCE_MISMATCH")입니다. 반대로 이 API 용 토큰은 MCP 에서 401 입니다. - 개인 API 키는 사용 대상 구분 없이 MCP 와 이 API 에 같이 쓸 수 있습니다(키에 고른 scope 그대로).
- 키는 발급 직후 한 번만 보입니다. 서버에만 보관하고, 노출이 의심되면 같은 화면에서 재발급하거나 폐기하세요.
- 발송 API 의
x-jober-openapi헤더는 받지 않습니다. 같은 회사 API 키라도 이 API 에는Authorization: Bearer로 보냅니다. - 요청마다 키·토큰 주인(회사 키는 키를 만든 관리자)이 지금도 그 스페이스 관리자인지 다시 확인합니다. 권한(scope)은 MCP 와 같습니다(권한 표).
회사 API 키 권한
회사 API 키 하나에 발송 API(OpenAPI v1)용 권한과 이 API 용 권한(AI 에이전트)을 함께 고를 수 있습니다. 권한을 고르지 않으면 openapi:document(문서 발송)만 들어갑니다.
| 권한(scope) | 화면 이름 | 쓰는 곳 | 줄 수 있는 사람 |
|---|---|---|---|
openapi:document | 문서 발송 | 발송 API의 문서·세트·서식 API | 스페이스 관리자 |
openapi:profile | 연락처 관리 | OpenAPI 연락처 연동 | 스페이스 관리자 |
openapi:space | 스페이스 생성 | OpenAPI 하위 스페이스 생성·관리자 초대 | 스페이스 소유자, 자버 관리자 |
openapi:sso | 로그인 연동 | OpenAPI 로그인 연동(소유자 계정으로 로그인) | 스페이스 소유자, 자버 관리자 |
campaign:read·campaign:write·contacts:write·survey:write·send:execute | 발송 조회·문구 작성·연락처 저장·설문 작성·메시지 발송(알림톡·이메일) | 이 API(뜻은 권한 표) | 스페이스 관리자 |
openapi:sso와 AI 에이전트 권한은 한 키에 함께 줄 수 없습니다(400J_41_25_1_3). 소유자 로그인 권한과 발송 권한이 한 비밀값에 모이지 않게 하기 위해서입니다.- 소유자 전용 권한(
openapi:space·openapi:sso)이 든 키를 소유자가 아닌 관리자가 발급·재발급하면 403J_41_25_3_7입니다. 폐기는 누구나 할 수 있습니다. - AI 에이전트 권한이 없는 회사 키로 이 API 를 부르면 403
J_41_25_3_3(data.requiredScope)입니다. 운영팀이 예전에 발급한 기존 키(jbr_co_로 시작하지 않는 키)는 키를 만든 사람이 없어 이 API 에 쓸 수 없고 401J_41_25_3_6(INVALID_TOKEN)입니다. 새 회사 키를 만드세요. - 활성 회사 키는 스페이스당 5개까지입니다(빠른 발송 API 콘솔의 문서 발송 키, 연동 업체 키 포함). 넘으면 409
J_41_25_4_3입니다.
키 발급·관리
회사 API 키와 개인 API 키는 자버에 관리자로 로그인해 설정 > AI 도구 연동에서만 발급·재발급·폐기합니다. 키 종류별 차이는 위 인증 표를 보세요.
- 회사 API 키: 스페이스에 속한 키입니다. 화면에서 권한을 골라 발급하고, 같은 이름·권한으로 재발급하면 이전 키는 바로 멈춥니다.
- 개인 API 키: 발급한 관리자에게 묶인 키입니다. 재발급 대신 폐기한 뒤 새로 만듭니다.
- 키 관리는 API 키나 OAuth 토큰으로 할 수 없습니다.
인증 오류
| HTTP | errorCode | 원인 |
|---|---|---|
| 401 | J_41_25_3_6 | 인증 실패. data.reason: MISSING_TOKEN(Bearer 헤더 없음), INVALID_TOKEN(없는·만료·폐기된 키·토큰, 만든 사람이 없는 기존 회사 키), NOT_MANAGER(주인 또는 회사 키를 만든 관리자가 그 스페이스 관리자가 아님), RESOURCE_MISMATCH(다른 사용 대상의 OAuth 토큰) |
| 403 | J_41_25_3_3 | 이 엔드포인트에 필요한 권한이 없음(data.requiredScope). AI 에이전트 권한이 없는 회사 키도 여기에 해당 |
| 403 | J_41_25_3_4 | 차단된 계정·스페이스이거나 차단된 접속 위치 |
| 404 | J_41_25_4_6 | 이 환경에서 자유 발송 API 가 아직 열려 있지 않음 |
| 404 | J_41_25_4_7 | 없는 경로이거나 지원하지 않는 메서드 |
| 400 | J_41_20_1_1 | 입력 형식이 틀림. data.issues: [{path, message}](최대 10개) |
| 429 | J_41_25_4_4 | 요청이 너무 많음. 연결(키 또는 OAuth 연결) 단위로 1분에 120번까지, Retry-After: 60 만큼 기다린 뒤 다시 시도(MCP 와 따로 셉니다) |
기본 주소와 응답 형식
| 항목 | 값 |
|---|---|
| 기본 주소 | https://api.jober.io/openApi/v2/agent |
| 형식 | 요청·응답 모두 JSON(Content-Type: application/json), UTF-8 |
| 호출 위치 | 서버 간 호출. 키를 브라우저·앱 코드에 넣지 마세요 |
성공하면 HTTP 200과 함께 {message, data} 가 옵니다. data 는 같은 이름의 MCP 툴 결과와 같은 값입니다.
{ "message": "", "data": { "campaign": { }, "guide": { } } }실패하면 4xx·5xx 와 함께 {message, errorCode, httpStatusCode, data} 가 옵니다.
{ "message": "보낼 수 없는 링크예요.", "errorCode": "J_41_22_1_14", "httpStatusCode": 400, "data": { "reason": "MALICIOUS", "restrictedKeywords": ["급전"] } }- 응답에
result필드는 없습니다. 성공·실패는 HTTP 상태로, 분기는errorCode로 하세요.message문구는 바뀔 수 있습니다. - 입력은 같은 이름의 MCP 툴과 같은 형식으로 검사하고, 모르는 키는 버립니다. 경로의
{uuid}가 본문campaignUuid보다 우선합니다. - 없는 경로는 HTML 이 아니라 JSON 404
J_41_25_4_7로 응답합니다. - 캠페인 응답의
campaign.guide와 발송 준비·실행 응답의guide가 다음에 물을 것과 부를 엔드포인트를 알려 줍니다(아래 질문과 다음 할 일).
보내는 흐름
- 사전 확인
GET /readiness: 채널 연동, 발송 한도의 남은 수(limits), 쓸 수 있는 기능, 지금이 광고성 알림톡 야간 시간인지, 권한과 막힌 이유(blockers)를 봅니다. - 초안 한 번에 만들기
POST /campaigns: 원문을 변환하고, 링크를 주면 버튼에 붙이고, 명단을 주면 확인해 조건이 맞으면 연락처에 저장까지 합니다. 광고·정보성, 채널(channel을 넣지 않으면channelDecision), 템플릿, 쿠폰명·추가 메시지 같은 빈칸은 서버가 정하고explanation{code, text, why}로 무엇을 어떻게 바꿨는지 알려 줍니다. 발송은 하지 않습니다. - 질문에 답하기
POST /campaigns/{uuid}/answers: 응답guide.questions에 남은 것(받는 사람 명단, 버튼 뒤 페이지, 광고 이메일의 보낸 곳 정보, 지어낼 수 없는 빈칸)만 사용자에게 묻고 답을 한 번에 보냅니다. 질문이 없어질 때까지 반복합니다. - 테스트 발송
POST /campaigns/{uuid}/test-send: 내용이 확정되면 1명에게 테스트로 보내 봅니다(테스트 발송). - 발송 준비
POST /campaigns/{uuid}/prepare-send: 광고면 받는 분 모두 광고 수신에 동의했는지 사용자에게 한 번 확인하고consentConfirmed: true로 부릅니다. 대상 수, 예상 비용, 미리보기, 자동 조정(summary.adjustments)과 확인값(operationUuid·confirmHash)을 받습니다. - 발송
POST /campaigns/{uuid}/submit-send: 확인값과Idempotency-Key헤더로 실행하거나 예약합니다. - 결과
GET /campaigns/{uuid}/results: 발송 문서가 아직 없으면statsAvailable은false,opened는null입니다(이메일도). 발송 중에는pending에 아직 문서가 생기지 않은(보내지 않은) 대상도 들어갑니다. 전달·열람(opened)·응답 수를 봅니다.opened는 알림톡이면 버튼으로 연 문서·페이지 수(내 링크면 버튼을 누른 수), 이메일이면 메일 안 링크를 누른 수입니다. 발송 중이라 아직 집계 전(발송 문서 0건)이거나 작업이 없거나 예약 대기면statsAvailable: false이니retryAfterSec초 뒤 다시 조회하세요. 제출 뒤 진행 상황은 캠페인의status로 봅니다(캠페인 상태).
1. 사전 확인
curl "https://api.jober.io/openApi/v2/agent/readiness?targets=30&nature=AD" \
-H "Authorization: Bearer jbr_live_xxxxxxxx"{
"message": "",
"data": {
"space": { "id": "…", "name": "하루피부과의원 잠실점" },
"channel": { "connected": false, "lookupFailed": false, "connectUrl": "https://jober.io/kakao-channel?connect=1&from=agent", "publicProfile": true },
"capabilities": {
"send": true, "directLink": true, "generatedLanding": true, "spaceTemplates": 12,
"couponFamilies": [{ "family": "COUPON_ISSUE", "approved": true }, { "family": "POINT_EXPIRY", "approved": false, "fallback": "COUPON_ISSUE" }]
},
"quietHours": { "active": true, "nextAllowedAt": "2026-11-01T23:00:00.000Z", "channels": { "KAKAO": { "appliesTo": "AD", "active": true, "nextAllowedAt": "2026-11-01T23:00:00.000Z", "autoSchedule": true, "message": "…" }, "EMAIL": { "appliesTo": null, "active": false, "nextAllowedAt": null, "autoSchedule": false, "message": "…" } } },
"scopes": ["campaign:read", "campaign:write", "contacts:write", "survey:write", "send:execute"],
"blockers": [],
"limits": [
{ "code": "FREE_DOCUMENT", "applies": false, "limit": null, "used": null, "remaining": null, "exempt": false, "errorCode": "J07994" },
{ "code": "UNCONNECTED_KAKAO", "applies": true, "limit": 10, "used": 4, "remaining": 6, "exempt": false, "errorCode": "J_07_53_01_1" },
{ "code": "AGENT_UNCONNECTED_LINK_AD", "applies": true, "limit": 3, "used": 1, "remaining": 2, "exempt": false, "errorCode": "J_41_23_1_13" },
{ "code": "PREPAID_BALANCE", "applies": true, "limit": null, "used": null, "remaining": 107, "exempt": false, "errorCode": "J_07_99_1_2", "balance": 5356, "estimatedUnitCost": 50 }
],
"subscriptionRequiredFor": [],
"notices": [
{ "code": "UNCONNECTED_PUBLIC_PROFILE", "message": "카카오 채널을 연동하지 않아 알림톡은 자버 공용 채널(자버문서)로 보내요. 알림톡의 외부 링크·광고 쿠폰 안내는 스페이스 누적 3명까지만 보낼 수 있어요(발송을 나눠도 합쳐 세요, 이메일은 해당 없음)." },
{ "code": "EMAIL_SHARES_KAKAO_TRIAL", "message": "카카오 채널 연동 전에는 이메일과 알림톡을 합쳐 10건까지 보낼 수 있어요. 충전해도 늘지 않고, 더 보내려면 카카오 채널 연동과 충전이 모두 필요해요." }
]
}
}channel.connected는 웹 발송 한도 기준의 채널 연동(유료 채널 연동) 여부이고,channel.publicProfile이true면 실제 발송이 자버 공용 채널로 나갑니다(링크·광고 3명 상한 대상).limits·subscriptionRequiredFor·notices는 채널 미연동 발송과 한도에 설명이 있습니다. readiness 는 발송 가능 여부를 단정하지 않습니다. 대상 수와 비용은 발송 준비에서 확정합니다.blockers값:SPACE_BLOCKED,MISSING_SCOPE:<scope>. 채널 항목(CHANNEL_NOT_CONNECTED·CHANNEL_LOOKUP_FAILED·INTEGRATION_FEE_UNPAID)은 자버가 비상시에 채널 연동을 다시 필수로 켰을 때만 나옵니다.capabilities.directLink(내 링크)·generatedLanding(페이지 생성)은 채널 연동과 무관하게survey:write권한으로 정해집니다.targets를 주면onboarding.targetStage가TARGET_MET또는TARGET_NOT_MET입니다(stage는 계정 준비 상태로 그대로). 목표에 필요한 단계는steps의optional이false,requiredForTargets가true입니다.targetStatus.channels[].message는 채널·성격별 안내이고, 알림톡은adMessage에 광고 쿠폰·외부 링크 기준(카카오 채널 연동 전 3명)을 따로 알려 줍니다.targets를 주면onboarding.targetStage를 먼저 보세요(TARGET_MET·TARGET_NOT_MET).stage는 계정 준비 상태입니다.steps[].targetState가NEEDED인 단계(OK가 아닌 단계)가 목표에 필요한 단계입니다.onboarding: 처음 쓰는 사람을 위한 안내.stage(ACCOUNT_READY·CHANNEL_PENDING·READY_TO_SEND·SENT_BEFORE),steps[](account·aiConnection·balance·kakaoChannel·firstSend와 상태),nextStep: {id, message}.message는 서버가 정한 문장이라 사용자에게 그대로 보여 주면 됩니다.channels[]: 순서는EMAIL·KAKAO·SMS로 고정입니다.EMAIL은available·reason(FLAG_OFF·MISSING_SCOPE)·sender(보내는 이름·주소),KAKAO는connected와 연동 준비물·연동비·체험 한도(setup),SMS는available·reason(FLAG_OFF·MISSING_SCOPE·SPACE_CHANNEL_DISABLED·CHANNEL_NOT_CONFIGURED·CHECK_FAILED)·natureAllowed: ["INFO"]·unitCostKrw: 50·sender(자기 번호OWN·자버 공용 번호PUBLIC)·quota·setup.settingsUrl입니다(문자로 보내기).testSend: 테스트 발송 하루 한도와 남은 수, 자동 채움 값{limit, used, remaining, resetAt, prefill{phone, email}, masked}(API 는 가운데를 가린 값,masked: true)입니다.links: 충전(prepaid), 요금제(pricing), 가입 직후 화면(welcome), 키 발급(apiKeys), 카카오 채널 연동(kakaoConnect,?connect=1이면 연동 창이 바로 열림) 주소입니다.- 쿼리
targets(보낼 인원, 1~100000)를 넣으면channels[].capacity에 지금 보낼 수 있는 수(now), 모자란 수(shortBy), 충분한지(enough), 해제 방법(unlock[]:CHARGE·CONNECT_KAKAO_CHANNEL·WAIT_DAILY_RESET), 둘 이상 필요한지(needsAllUnlocks)가 나옵니다.basis는 이 값들의 기준입니다(nature입력, 없으면INFO).byNature.INFO는 정보성 알림톡·이메일 기준,byNature.AD는 광고 쿠폰·외부 링크 알림톡 기준입니다(카카오 채널 연동 전 3명 상한 포함). 광고 쿠폰 알림톡을 보낼 거라면byNature.AD를 보거나nature: "AD"를 함께 넣으세요. 근거 값은byBalance(잔액 ÷ 건당 단가)·byTrial(체험 남은 수)·byDaily(이메일 하루 상한 남은 수)·byLinkAd(링크·광고 3명 남은 수)입니다. 이메일 한도는channels[EMAIL].quota(sharedWithKakao,remaining,unlock)에 있고,channels[EMAIL].quota.unlock은 해제 방법 배열[{code, action, url, resetAt}]입니다(없으면 빈 배열). 명단을 만들기 전에 사용자에게 먼저 알리세요. onboarding.targetStatus(targets를 줬을 때):met(쓸 수 있는 채널로 목표를 채우는지),requiredSteps(kakaoChannel·balance),message,channels[]. 모자라면steps[].requiredForTargets가true이고nextStep이 목표 기준 안내입니다(stage는 그대로).quietHours.channels(KAKAO·EMAIL)에 채널별 야간 규칙이 있습니다(기존active·nextAllowedAt은 그대로).balanceAsOf는 잔액·한도를 읽은 시각입니다. 예시의 건당 예상 단가(estimatedUnitCost)는 문서 유형 단가(기본 약 50원)입니다.
2. 초안 만들기
Idempotency-Key 헤더(8~80자, 영문·숫자·_.:-)를 넣으면 같은 요청을 다시 보내도 캠페인이 하나만 생깁니다. 같은 키에 같은 내용이면 reused: true 로 지금 상태를 돌려주고, 내용이 다르면 409 J_41_21_4_5 입니다. 키는 헤더로만 받습니다(본문의 requestKey 는 무시). 형식이 틀리면 400 J_41_23_1_12 입니다.
curl -X POST "https://api.jober.io/openApi/v2/agent/campaigns" \
-H "Authorization: Bearer jbr_live_xxxxxxxx" \
-H "Idempotency-Key: 3f2c9e1a-1101-4b8e-9c55-1d0e5a7b2f10" \
-H "Content-Type: application/json" \
-d '{
"sourceText": "[하루피부과의원 잠실점] 고객님의 적립금 20,000원이 11월 30일에 소멸됩니다. 11월 리프팅 이벤트도 확인하세요.",
"link": { "url": "https://haru-derm.co.kr/event/1111" },
"recipients": { "headers": ["이름", "휴대폰"], "rows": [["김하루", "01012345678"], ["이도윤", "01023456789"]] }
}'| 필드 | 필수 | 설명 |
|---|---|---|
sourceText | 예 | 보내려는 문자 원문 그대로(최대 4,000자) |
sendName | 아니요 | 발송명(관리용, 최대 100자). 생략하면 원문에서 만듭니다 |
link.url | 아니요 | 버튼에 붙일 내 링크(아래 메시지+링크) |
landing | 아니요 | 버튼 뒤 페이지. AUTO(기본, link 가 있으면 그 링크 아니면 질문으로 남김), URL, GENERATE_INFO(안내 페이지 생성), GENERATE_SURVEY(설문 생성), NONE(링크 없이) |
recipients | 아니요 | 받는 사람 표 {headers, rows}(최대 5,000행). 이름·전화 열이 하나씩만 보이고 덮어쓸 칸이 없을 때만 바로 저장하고, 아니면 미리보기와 COLUMN_MAPPING 질문을 돌려줍니다 |
응답 data: {campaign, guide, conversion, job, recipientPreview, recipientCommit, reused, failedSteps}. 링크·명단 단계가 실패해도 캠페인은 남고 failedSteps[{step, reason, errorCode}] 에 이유가 있습니다. link.url 을 주면서 landing 을 GENERATE_INFO·GENERATE_SURVEY·NONE 으로 보내면 서로 맞지 않아 400 J_41_21_1_6 입니다(link.url 이 있으면 landing 은 AUTO 또는 URL). 페이지를 만들려면 link 를 빼세요. conversion 은 광고 원문에서 무엇을 지우고(removed) 무엇을 버튼 뒤로 옮기고(movedToLanding) 무엇을 앞에 남겼는지(kept)입니다.
3. 질문에 답하기
광고 수신 동의는 질문으로 묻지 않고 발송 준비의 consentConfirmed 하나로 받습니다(예전 sendBasis 답도 받으며, 같은 내용이면 prepare_send 인자에 consentConfirmed 가 채워집니다). 선택 질문만 남으면 answer_questions 의 nextActions 가 blocking: false 입니다. 알림톡의 받는 사람 이름(수신자)·보낸 곳(발신 스페이스) 칸은 발송 때 바뀌는 값이라 고칠 수 없습니다(editable: false, fixedTo: "RECIPIENT_NAME"·"SPACE_NAME", 고치려 하면 400 J_41_21_1_4). 보낸 곳 이름은 자버 스페이스 이름(웹 스페이스 설정)을 따릅니다.
정보성 알림톡의 쿠폰명에는 혜택 상세(예: 1잔 무료, 20% 할인)를 넣을 수 없습니다. 들어 있으면 서버가 지우고 AD_CONTENT_REPLACED 로 알립니다(지우고 남는 것이 없으면 쿠폰). 정보성 알림톡의 쿠폰명·사용기한·쿠폰확인방법·이용권명에 할인율·1+1·증정·무료 제공·이벤트·세일 같은 광고 표현을 넣으면 400 J_41_21_1_4(data.reason: "AD_CONTENT_IN_INFO")이고, 발송 준비에서도 같은 기준으로 400 J_41_23_1_1(PROMOTIONAL_COPY)입니다. 혜택을 알리려면 광고로 보내세요. 정보성 빈칸의 쿠폰·홍보 문구를 서버가 기본 안내문으로 바꿔도 경고 AD_CONTENT_REPLACED 가 나옵니다(하위 호환으로 같은 내용의 INFERRED_VALUE(reason: "AD_CONTENT_IN_INFO")도 함께 오니 둘 중 하나만 보여 주세요). answer_questions(REST 답하기) 결과의 replaced[] 에도 바꾼 칸이 있고, 같은 경고가 발송 준비 summary.warnings 에도 남습니다(템플릿이 바뀌어도 유지).
curl -X POST "https://api.jober.io/openApi/v2/agent/campaigns/{uuid}/answers" \
-H "Authorization: Bearer jbr_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"revision": 3,
"answers": [
{ "id": "landing.link", "value": "URL", "url": "https://haru-derm.co.kr/event/1111" }
]
}'revision은 최신 캠페인 값입니다(다르면 409J_41_21_4_2).guide.nextActions[].args에 미리 채워져 있습니다.- 답 형식이 하나라도 틀리면 아무것도 적용하지 않고 400
J_41_21_1_6입니다. 권한이 없거나 서버가 거절한 답은 그 답만skipped[{id, reason, errorCode}]에 남습니다. - 여러 단계를 이어서 바꾸는 중에 다른 요청이 캠페인을 먼저 바꾸면 HTTP 200 응답 안에서 남은 단계만 멈춥니다. 멈춘 항목은
skipped(초안은failedSteps)에errorCode: "J_41_21_4_2"와data.stoppedBy(멈추게 한 단계)로 남습니다.GET /campaigns/{uuid}로 최신 캠페인을 다시 읽고 남은 질문에 다시 답하세요. - 응답
data:{campaign, guide, applied, skipped, job, recipientPreview, recipientCommit}.
4. 발송 준비
curl -X POST "https://api.jober.io/openApi/v2/agent/campaigns/{uuid}/prepare-send" \
-H "Authorization: Bearer jbr_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "consentConfirmed": true, "revision": 4 }'{
"message": "",
"data": {
"operationUuid": "op-…",
"confirmHash": "…",
"expiresAt": "2026-11-01T13:00:00.000Z",
"summary": {
"targets": 214,
"estimatedCost": 10700,
"scheduledAt": "2026-11-01T23:00:00.000Z",
"quietHours": { "blocked": false, "nextAllowedAt": null },
"adjustments": [{
"code": "QUIET_HOURS_RESCHEDULED",
"requestedAt": null,
"scheduledAt": "2026-11-01T23:00:00.000Z",
"message": "광고성 알림톡이라 밤 9시~아침 8시를 피해 11월 2일 08:00에 보내도록 예약했어요."
}],
"warnings": [],
"limits": [
{ "code": "FREE_DOCUMENT", "applies": false, "limit": null, "used": null, "remaining": null, "exempt": false, "errorCode": "J07994" },
{ "code": "UNCONNECTED_KAKAO", "applies": false, "limit": null, "used": null, "remaining": null, "exempt": false, "errorCode": null },
{ "code": "AGENT_UNCONNECTED_LINK_AD", "applies": false, "limit": null, "used": null, "remaining": null, "exempt": false, "errorCode": "J_41_23_1_13" },
{ "code": "PREPAID_BALANCE", "applies": true, "limit": null, "used": null, "remaining": 993, "exempt": false, "errorCode": "J_07_99_1_1", "balance": 49660, "estimatedUnitCost": 50 }
],
"notices": [],
"renderedPreview": "안녕하세요, 남궁하루라님! …",
"longestRenderedLength": 236
},
"guide": { "stage": "PREPARED", "questions": [], "nextActions": [{ "tool": "submit_send", "rest": "POST /openApi/v2/agent/campaigns/…/submit-send", "blocking": true }] }
}
}scheduledAt(시간대가 들어간 ISO 8601, 예2026-11-02T10:00:00+09:00)을 생략하면 지금 보냅니다. 광고성 알림톡(광고·쿠폰 안내)을 밤에 준비하면 서버가 묻지 않고 예약 시각 이후 첫 08:00(한국 시간)으로 옮깁니다(아래 야간 자동 예약). 정보성 알림톡과 이메일(광고성 포함)은 야간에도 자동 예약 없이 바로 보냅니다.consentConfirmed는 광고·쿠폰 안내일 때 받는 분 모두 광고 수신에 동의했다는 확인입니다(쿠폰 안내는 그 혜택을 실제로 지급했는지도). 따로 묻는 질문이 없으니 사용자에게 한 번 확인하고 넣으세요. 광고인데 없으면 400J_41_23_1_3입니다.scheduledAt을 생략하면PATCH /campaigns/{uuid}의scheduledAt으로 정해 둔 시각(없으면 지금)을 씁니다.- 문자 캠페인은
summary.estimatedCost가 50원 × 대상 수입니다. 야간에도 예약하지 않고 바로 보내며, 밤 9시~아침 8시면summary.warnings에SMS_NIGHT_NOTICE, 휴대전화가 아닌 번호를 빼면SMS_NON_MOBILE_EXCLUDED가 붙습니다. - 확인값은 10분 동안 유효합니다. 그사이 내용이 바뀌면 발송이 거부되고 다시 준비해야 합니다.
prepare_send의summary.warnings에는 캠페인 응답과 같은 경고(LANDING_NONE_DROPPED·KAKAO_CORE_MISSING·AD_CONTENT_REPLACED·EMAIL_FACT_NOT_IN_BODY)가 함께 나옵니다.summary.limits·summary.notices는 이 캠페인 기준 한도와 안내입니다(채널 미연동 발송과 한도). 한도를 넘으면 준비가 거절되고, 거절 응답의data.limits·data.notices에 같은 값이 옵니다.- 버튼 뒤 목적지(설문·공유 페이지·내 링크)가 있는데 템플릿에 링크 버튼이 없으면 409
J_41_23_4_4(data.reason: "NO_BUTTON_TEMPLATE")입니다. 버튼 없이 바꿔 보내지 않습니다. 링크 없이 보내려면landing을NONE으로 고른 뒤 다시 준비하세요. - 쿠폰명 고치기(
PATCH·답하기): 과장·한정·최대·권유 표현이면 400J_41_21_1_4(data.forbidden, 고칠 수 없어 다시 적어야 함), 할인율·홍보 단어·감탄 기호는 서버가 지우고 저장한 뒤 경고INFERRED_VALUE(expressions)로 알립니다. 받는 사람마다 다른 연락처 칸 값은 고칠 수 없어 발송 준비 때 아래PROMOTIONAL_COPY로 막습니다. - 문구 검사에 걸리면 400
J_41_23_1_1이고data.errors[]에{code, key?, message}가 옵니다.PROMOTIONAL_COPY는 쿠폰명·추가 메시지(받는 사람마다 다른 연락처 칸 값 포함)에 홍보 표현이 남은 경우로, 연락처 칸 값이 원인이면count(해당 받는 사람 수)와examples(값 예시 최대 몇 개)가 함께 옵니다.BUTTON_TARGET_FIXED는 템플릿 버튼 주소가 고정돼 내 링크를 붙일 수 없는 경우입니다.
5. 발송
Idempotency-Key 헤더가 필수입니다(없거나 형식이 틀리면 400 J_41_23_1_12, 본문의 idempotencyKey 는 무시). 같은 키로 다시 부르면 새로 보내지 않고 처음 결과를 돌려줍니다(replayed: true).
curl -X POST "https://api.jober.io/openApi/v2/agent/campaigns/{uuid}/submit-send" \
-H "Authorization: Bearer jbr_live_xxxxxxxx" \
-H "Idempotency-Key: 9b1d7c52-3e0a-4f6b-8a21-6c4d2e9f0a77" \
-H "Content-Type: application/json" \
-d '{ "operationUuid": "op-…", "confirmHash": "…" }'응답 data: {documentTaskDefinitionId, status, replayed?, guide}. status 는 SENDING(즉시) 또는 SCHEDULED(예약)입니다. 스팸 검토로 접수만 되고 발송 작업이 아직 없으면 documentTaskDefinitionId 는 null 입니다. 발송이 접수되면 스페이스 관리자에게 'API 발송' 알림이 가고, 예약은 POST /campaigns/{uuid}/cancel 로 취소할 수 있습니다. 재시도·접수 대기(J_41_23_4_5)·다시 준비(data.needsPrepare) 처리 규칙은 MCP 와 같습니다(발송 안전장치).
- 발송 실행 때도 채널과 발송 한도를 다시 확인합니다. 그사이 링크·광고 3명 상한을 다 썼으면 409
J_41_23_1_13, 체험 10건을 넘으면J_41_23_1_9(data.sourceErrorCode)입니다. 두 경우 모두 다시 준비해도 풀리지 않으니 받는 사람을 줄이거나 채널을 연동하세요. - 지금 보내기로 준비했는데 실행하는 사이 밤 9시가 지나면(광고성 알림톡만, 정보성 알림톡·이메일은 해당 없음) 400
J_41_23_1_2(data.needsPrepare: true,data.nextAllowedAt)입니다. 다시 준비하면 예약 시각 이후 첫 08:00(한국 시간)으로 자동 예약됩니다.
질문과 다음 할 일 (guide)
캠페인 응답의 data.campaign.guide(발송 준비·실행·작업 조회는 data.guide)는 아래 형태로 지금 단계(stage), 사용자에게 물을 질문(questions), 다음에 부를 엔드포인트(nextActions)를 알려 줍니다. 서버가 상태에서 매번 계산하며 저장하지 않습니다.
{
"stage": "NEEDS_LANDING",
"questions": [{
"id": "landing.link",
"kind": "LANDING_LINK",
"required": true,
"prompt": "버튼을 눌렀을 때 열릴 링크(이벤트 페이지 등)가 있나요? 없으면 자세히 보기 페이지를 만들어 붙일게요.",
"answerType": "choice",
"options": [
{ "value": "URL", "label": "내 링크 붙이기", "needs": "url" },
{ "value": "GENERATE_INFO", "label": "안내 페이지 만들기" },
{ "value": "GENERATE_SURVEY", "label": "설문 만들기(응답 받기)" },
{ "value": "EXISTING", "label": "기존 설문·공유 페이지 고르기" },
{ "value": "NONE", "label": "링크 없이 보내기" }
],
"recommended": { "value": "URL", "url": "https://haru-derm.co.kr/event/1111" },
"context": { "sourceUrls": ["https://haru-derm.co.kr/event/1111"], "landingReason": "AD_DETAIL", "channelConnected": true }
}],
"nextActions": [{ "tool": "answer_questions", "rest": "POST /openApi/v2/agent/campaigns/…/answers", "reason": "링크 질문에 답해야 다음 단계로 갈 수 있어요.", "blocking": true, "args": { "revision": 3 } }]
}광고·정보성은 묻지 않고 서버가 정합니다(analysis.natureDecision). 원문 분석과 서버 규칙 중 하나라도 광고면 광고이고, 이미 드린 혜택의 발급·도착·만료 안내처럼 둘 다 정보성일 때만 정보성입니다. 광고·정보성이 섞인 원문(MIXED)과 혜택 신호가 있는 원문은 광고로 만들고, 알림톡은 앞단을 쿠폰 안내로 줄이고 홍보 내용을 버튼 뒤 페이지로 옮깁니다(explanation). 사용자가 정보성으로 보내 달라고 하면 {id: "nature", value: "INFO"} 또는 PATCH 의 natureChoice: "INFO" 로 보내고, 할인·홍보 표현이 남아 있으면 서버가 광고를 유지하고 explanation.code: "AD_KEPT_ON_INFO_REQUEST"(200)로 이유를 알려 줍니다. 원문 분석이 광고인 캠페인은 400 J_41_21_1_4 입니다. 정보성에서 광고로 바꾸는 것(natureChoice: "AD")은 언제든 할 수 있습니다. 광고 알림톡에서 원문의 금액·혜택(숫자 표현)이 메시지에 없으면 KAKAO_CORE_MISSING 경고(expressions·texts)가 나옵니다. 알림톡 앞단에는 가격·홍보 문구를 넣을 수 없어서 버튼 뒤 페이지에 넣는 것이 기본입니다. 알림톡의 받는 사람 이름(수신자)·보낸 곳(발신 스페이스) 칸은 발송 때 바뀌는 값이라 고칠 수 없습니다(editable: false, fixedTo: "RECIPIENT_NAME"·"SPACE_NAME", 고치려 하면 400 J_41_21_1_4). 보낸 곳 이름은 자버 스페이스 이름(웹 스페이스 설정)을 따릅니다. 이메일은 원문에 쿠폰·무료 제공·할인 같은 혜택 안내가 있으면 '(광고)'를 적지 않아도 광고 이메일로 보냅니다(경고 AD_RECLASSIFIED). 제목에 (광고) 가 붙고, 받는 분의 광고 수신 동의 확인(발송 준비의 consentConfirmed)과 보내는 곳 이름·연락처가 필요합니다.
stage: NEEDS_INPUT → NEEDS_LANDING → NEEDS_RECIPIENTS → READY_TO_PREPARE → PREPARED → IN_FLIGHT → DONE, 막히면 BLOCKED.
campaign.status 는 저장 상태이며, 다음 행동은 guide.stage 를 따릅니다. status 가 READY 여도 guide.stage 가 BLOCKED·NEEDS_INPUT 이면 그 질문부터 풉니다. 명단을 확정했는데 한도나 잔액이 모자라면 다른 질문이 남아 있어도 guide.stage 가 BLOCKED 가 되고, questions 맨 앞에 SEND_LIMIT_EXCEEDED·BALANCE_INSUFFICIENT(context.shortBy)가 나옵니다(둘 다 해당하면 둘 다). 코드 값은 응답 코드 모음에 있습니다.
| kind | 언제 | 답(answers[]) |
|---|---|---|
SLOT_VALUE | 원문에 없는 값(쿠폰명·사용기한 등)이 비었을 때. id 는 slot.<변수키> | {id, value} |
RECONVERT | 알맞은 템플릿을 못 찾음. 답할 수 없음 | 원문을 보강해 POST /campaigns 로 다시 |
NATURE_CHOICE | 더 나오지 않습니다(하위 호환 값만 남음). 광고·정보성은 서버가 정하고 발송 준비를 막지 않습니다 | 사용자가 바꾸고 싶다고 할 때만 {id: "nature", value: "AD" 또는 "INFO"} |
LANDING_LINK | 버튼 뒤 페이지가 아직 없음. 원문에 주소가 있으면 그 주소를 추천 | {id: "landing.link", value, url?}. EXISTING 이면 frameId 또는 shareOptionId(GET /surveys) |
SEND_LIMIT_EXCEEDED | 받는 사람을 확정했더니 대상 수가 한도의 남은 수보다 많음(stage: "BLOCKED", context: {code, remaining, limit, targets, shortBy, resetAt, connectUrl}, 다른 질문보다 먼저). 답할 수 없음. SEND_LIMIT_EXCEEDED 의 CHARGE 선택지(충전하기(카카오 채널 연동과 함께 필요))에 requiresWith: "CONNECT_CHANNEL" 이 있으면 충전만으로는 풀리지 않고 카카오 채널 연동과 함께 필요하다는 뜻입니다. context 에는 늘 resetAt 이 있습니다(하루 상한이 아니면 null) | 선택지(REDUCE_RECIPIENTS 받는 사람 줄이기, CONNECT_CHANNEL 채널 연동, CHECK_PLAN 요금제 확인)를 사용자에게 보여 주고, 명단을 줄여 다시 확정하거나 조건을 해결한 뒤 다시 조회 |
CHANNEL_REQUIRED | 자버가 비상시에 채널 연동을 다시 필수로 켰을 때만. 답할 수 없음(context.connectUrl) | 채널을 연동한 뒤 다시 조회 |
RECIPIENTS | 받는 사람이 없음 | {id: "recipients", headers, rows} |
COLUMN_MAPPING | 이름·전화 열 후보가 여럿이거나 덮어쓸 칸이 있음 | {id: "recipients.columnMap", headers, rows, columnMap, customColumnMap?, confirmOverwriteKeys?} |
SEND_BASIS | 더 나오지 않습니다(하위 호환 값만 남음). 광고면 발송 준비의 consentConfirmed: true 로 받습니다 | 예전처럼 {id: "sendBasis", value: true} 로 답해도 받습니다 |
BALANCE_INSUFFICIENT | 명단을 확정했더니 예상 비용이 잔액보다 큼(stage: "BLOCKED", context: {balance, estimatedCost, shortBy, affordable, shortByRecipients}). 답할 수 없음 | 선택지(CHARGE 충전 url, CHECK_PLAN 요금제 url, REDUCE_RECIPIENTS 받는 사람 줄이기)를 사용자에게 보여 주고, 충전하거나 명단을 줄인 뒤 다시 |
EMAIL_SENDER_INFO | 광고 이메일인데 보낸 곳 정보(이름·연락처)가 없음(id: "email.sender", answerType: "senderInfo", context: {missing, prefill}) | {id: "email.sender", value: {name, phone?, email?, address?}} |
- 야간 시각과 수신거부는 묻지 않습니다. 광고성 알림톡의 야간은 발송 준비가 자동으로 맞추고(정보성 알림톡과 이메일은 야간에도 바로), 수신거부 링크는 기본으로 켜집니다.
- 채널을 연동하지 않아도 링크 질문(
LANDING_LINK)에는 모든 선택지가 나옵니다. 링크·광고 3명 상한의 남은 수가 0 이면URL(내 링크 붙이기)만 빠집니다. - 질문의
prompt·options는 서버 고정 문구입니다. 사용자 데이터(원문 주소 등)는context에만 들어 있습니다.
캠페인 상태
캠페인의 status 는 저장 상태입니다(조회할 때 발송 작업과 스팸 검토 기록으로 맞춰 돌려줍니다). 다음 행동은 status 가 아니라 guide.stage 를 따릅니다(guide). 웹 채팅, MCP, 이 API 가 모두 같은 규칙으로 돌려주므로 어디서 보든 같습니다.
| status | 뜻 | 할 수 있는 것 |
|---|---|---|
DRAFT | 초기값(지금은 거의 쓰이지 않음) | 수정 |
NEEDS_INPUT | 빠진 값이나 확인할 것이 있음 | guide.questions 에 답하기 |
READY | 발송 준비 가능 | 수정, 발송 준비 |
PREPARED | 발송 준비 완료(확인값 발급) | 발송 실행, 수정(수정하면 다시 준비) |
BLOCKED | 스팸·금지 표현으로 막힘 | 원문을 고쳐 새로 만들기 |
SCHEDULED | 예약 접수됨(스팸 검토 대기 중인 예약 포함) | 예약 취소 |
SENDING | 발송 중, 접수 결과를 확인하는 중, 또는 스팸 검토를 기다리는 즉시 발송 | 결과 조회 |
SENT | 발송 완료 | 결과 조회, 재알림 준비 |
CANCELED | 취소·중단됨(canceledReason 참고) | 결과 조회 |
FAILED | 발송 작업을 찾을 수 없음 | 결과의 notes 확인, 자버 고객센터 문의 |
canceledReason:SCHEDULE_CANCELED(예약 취소),SEND_STOPPED(발송 도중 중단 또는 스팸 검토에서 거부, 보내기 전에 멈췄으면 나간 메시지가 없을 수 있음), 그 밖은null.- 상태 값은 이 10가지뿐입니다. 발송 실행을 접수하는 중 서버가 멈춰 접수되지 않은 것이 확인되면(접수 5분 경과 또는 거절) 다음 조회에서
READY로 돌아가 다시 준비할 수 있습니다.
접수 결과를 확인 중인 발송 (pendingOperation)
GET /campaigns/{uuid} 의 pendingOperation 이 null 이 아니면 발송 실행을 접수하는 중입니다(status: "SENDING").
{ "operationUuid": "op-…", "confirmHash": "…", "scheduledAt": null, "status": "SUBMITTING", "sourceErrorCode": null, "lastErrorCode": null }pendingOperation.status는SUBMITTING하나입니다. 판정할 수 없다는 뜻의 값(UNKNOWN)은 더 이상 오지 않습니다.- 그 발송을 처음 보낸
Idempotency-Key를 갖고 있으면 같은 확인값·같은 키로submit-send를 다시 부르세요. 서버가 접수 결과를 돌려줍니다. 키가 없으면 새로 준비하거나 새 키로 보내지 말고 잠시 뒤 다시 조회만 하세요(두 번 발송될 수 있습니다). - 접수가 5분 안에 끝나지 않으면 서버가 정리해 캠페인이
READY로 돌아갑니다. 이때만 다시 준비하세요. - 스팸 검토로 접수된 발송이나 발송 작업이 생긴 뒤에는
pendingOperation이null이고 상태는SCHEDULED·SENDING등으로 보입니다.
예약 취소
POST /campaigns/{uuid}/cancel 은 SCHEDULED 인 캠페인을 취소하고 {status: "CANCELED"} 를 돌려줍니다(이미 취소됐으면 같은 응답). 스팸 검토를 기다리는 예약도 취소할 수 있고, 취소하면 canceledReason 이 SCHEDULE_CANCELED 입니다. 그 사이 관리자가 승인해 발송을 처리하고 있으면 409 J_41_23_4_6(data.approvalInProgress: true)이니 잠시 뒤 캠페인을 다시 조회하세요. 이미 발송 중·완료면 409 J_41_23_4_6(data.status)입니다.
채널 미연동 발송과 한도
카카오 채널 연동은 필수가 아닙니다. 연동하지 않은 스페이스도 자유 발송 API 로 보낼 수 있고, 그 경우 자버 공용 채널(자버문서)로 나가며 웹에서 보낼 때와 같은 검사(스팸·공용 채널 금지 표현·구독·선불 잔액)를 거칩니다. 여기에 아래 한도가 걸립니다.
limits[].code | 언제 적용 | 한도 | 세는 법 | 넘으면 |
|---|---|---|---|---|
FREE_DOCUMENT | 요금제 구독이 없을 때 | 10 | 공유 폴더·폴더 없는 문서(삭제한 문서 제외) | J_41_23_1_9(data.sourceErrorCode: "J07994") |
UNCONNECTED_KAKAO | 채널 미연동(channel.connected: false) | 10 | 보낸 문서(삭제한 문서 포함) + 접수 중인 AI 에이전트 발송 | J_41_23_1_9(data.sourceErrorCode: 구독 없음 J_07_53_01_2, 그 밖 J_07_53_01_1) |
AGENT_UNCONNECTED_LINK_AD | 자버 공용 채널로 나가는(channel.publicProfile: true) 내 링크(메시지+링크) 또는 광고 쿠폰 안내 | 3 | 웹 채팅 AI 에이전트·MCP·이 API 로 보낸 받는 사람 수 누적(삭제와 무관, 스팸 검토 거부·테스트 발송 포함) | 409 J_41_23_1_13 |
AGENT_EMAIL_NEW_SPACE_DAILY | 이메일, 가입 초기 하루 상한이 켜진 설정에서 가입 30일 안이거나 충전한 적 없는 스페이스 | 하루 200명(기본) | 그날(한국 시간) 에이전트 이메일 받는 사람 수 | 409 J_41_23_1_16(data.resetAt) |
PREPAID_BALANCE | 선불 요금제 | 없음 | 잔액으로 보낼 수 있는 건수(balance, estimatedUnitCost) | 409 J_41_23_1_14(data.sourceErrorCode: 연동 J_07_99_1_1, 미연동 J_07_99_1_2. 충전 data.prepaidUrl·요금제 data.pricingUrl 함께) |
- 항목 모양:
{code, applies, limit, used, remaining, exempt, errorCode}.applies: false면limit·used·remaining이null입니다. 예외로 이메일 가입 초기 하루 상한(AGENT_EMAIL_NEW_SPACE_DAILY)은applies: false여도limit에 수치(기본 200)를 줍니다. 집계에 실패한 항목은used·remaining만null입니다. - 체험 한도 면제 고객(일부 기존 요금제)은
UNCONNECTED_KAKAO가exempt: true,remaining: null입니다. 링크·광고 3명 상한은 면제 고객에게도 적용됩니다. - 실리는 곳:
GET /readiness의limits, 발송 전 캠페인(DRAFT·NEEDS_INPUT·READY·PREPARED)의campaign.limits(그 뒤 상태는null), 발송 준비의summary.limits, 한도 거절의data.limits. 캠페인·발송 준비 기준이면 3명 상한은 그 캠페인이 내 링크 또는 광고 쿠폰 안내일 때만applies: true입니다. - 한도는 발송 준비와 발송 실행 때 두 번 확인합니다. AI 에이전트 발송끼리는 스페이스 단위로 차례로 세므로 동시에 보내도 한도를 넘겨 접수되지 않습니다.
- 카카오 채널 연동 전 외부 링크·광고 쿠폰 알림톡은 받는 사람 스페이스 누적 3명까지입니다(발송을 나눠도 합쳐 셉니다).
J_41_23_1_13의data:{code: "AGENT_UNCONNECTED_LINK_AD", limit: 3, used, remaining, connectUrl}. 받는 사람을 남은 수 이하로 줄이거나connectUrl에서 채널을 연동하세요.subscriptionRequiredFor: 요금제 구독이 없으면 늘["SCHEDULED", "VARIABLE_LINK"](예약 발송과 받는 사람마다 값이 다른 링크는 구독 필요), 구독이 있으면[]입니다. 지금 시각·캠페인과 무관하게 스페이스 구독 여부로만 정해집니다.notices[]({code, message}):UNCONNECTED_PUBLIC_PROFILE(공용 채널로 나감, 3명 상한 안내),QUIET_HOURS_SUBSCRIPTION(구독이 없으면 시간과 무관하게 늘 붙음: 광고성 알림톡이 야간에 걸리면 예약 발송으로 바뀌는데 예약 발송은 구독이 필요함. 이메일 캠페인에는 붙지 않음).message를 사용자에게 그대로 보여 주면 됩니다.- 버튼 템플릿: 채널을 연동하지 않아도 링크 버튼이 있는 템플릿으로 보냅니다. 목적지가 있는데 맞는 버튼 템플릿이 없으면 409
J_41_23_4_4(data.reason: "NO_BUTTON_TEMPLATE")이고, 버튼 없이 바꿔 보내지 않습니다.
이메일로 보내기
초안을 만들 때 channel: "EMAIL" 을 넣으면 이메일 캠페인이 됩니다. 넣지 않으면 지금처럼 카카오 알림톡(KAKAO)입니다.
| 항목 | 내용 |
|---|---|
| 채널 값 | KAKAO, EMAIL, SMS(문자로 보내기, 문자가 꺼진 서버면 400 J_41_21_1_8), 그 밖의 값은 400 J_41_21_1_7. 넣지 않으면 서버가 정합니다(원문·요청에 메일 언급이 있거나 명단이 이메일뿐이면 EMAIL, 그 밖 KAKAO, 결과 channelDecision) |
| 받는 사람 | 명단의 이메일 열(recipients.columnMap.email). 이메일 열이 없으면 400 J_41_22_4_19, 보낼 수 있는 주소가 하나도 없으면 400 J_41_22_4_18. 주소가 없거나 형식이 틀린 사람과 수신거부한 사람은 빼고 보냅니다 |
| 보내는 주소 | 기본은 no-reply@jober.io(이름 자버 Jober)이고, 스페이스에 맞춤 발신자가 있으면 그 이름·주소로 나갑니다(readiness.channels[EMAIL].sender 의 display·hint). 맞춤 발신자(보낸 사람 표시명·주소)는 웹에서 직접 바꾸는 화면이 없어 도입 문의 또는 고객센터(02-6402-0508)로 요청하면 자버가 설정합니다. 설정되면 readiness.channels[EMAIL].sender 의 custom 이 true 가 됩니다. 광고 이메일 본문 아래 보낸 곳 정보(이름·연락처)는 맞춤 발신자와 별개로 email.sender 답으로 넣습니다 |
| 성격 판정 | 원문에 쿠폰·무료 제공·할인 같은 혜택 안내가 있으면 '(광고)'를 적지 않아도 광고 이메일로 보냅니다(경고 AD_RECLASSIFIED). 제목에 (광고) 가 붙고, 받는 분의 광고 수신 동의 확인(발송 준비의 consentConfirmed)과 보내는 곳 이름·연락처가 필요합니다. 정보성 이메일에는 (광고) 와 본문 보낸 곳 정보를 넣지 않습니다(수신거부 링크는 기본으로 넣고 optOutLink: false 로 뺄 수 있음, 미리보기 값 email.senderLine·unsubscribeEnLine·optOutFooter) |
| 핵심 혜택 보존 | 이메일 문구에서 원문의 혜택·기간(숫자 표현, 예: 1잔·50%·5,000원·11월 9일)이 빠지면 서버가 그 원문 문장을 그대로 넣고 경고 EMAIL_CORE_RESTORED(expressions·texts)로 알립니다 |
| 확정한 내용 | 선택 질문에 답한 내용은 email.confirmedFacts 에 있고 본문에 들어갑니다. 보이는 본문에서 빠지면 경고 EMAIL_FACT_NOT_IN_BODY(slot·texts)가 나옵니다 |
| 링크 질문 | 이메일은 링크가 정해지지 않았으면 링크 질문(landing.link)이 늘 나옵니다(본문 상세를 옮겨야 하면 필수, 아니면 선택이고 단계를 막지 않음). 질문은 '메일에 넣을 링크(이벤트 페이지 등)가 있나요? 없으면 안내 페이지를 만들어 링크로 넣거나 링크 없이 보낼 수 있어요.' 처럼 '버튼' 대신 '링크'로 묻습니다 |
| 링크 없이 보내기 | 이메일에서 링크 없이 보내기(NONE)를 고르면 서버가 문구를 다시 만들고(revision 이 한 번 더 오름) 버튼을 가리키는 문장을 뺍니다(email.ctaLabel 은 null) |
| 광고성 이메일 | 제목 앞 (광고) 를 서버가 한 번만 붙이고, 보낸 곳 이름·연락처와 수신거부 링크를 넣습니다. 보낸 곳 정보가 없으면 400 J_41_23_1_15 와 guide 질문 email.sender(answerType: "senderInfo") |
| 요금·한도 | 알림톡과 같은 기준(건당 약 50원)으로 선불 잔액에서 빠집니다. 카카오 채널을 연동하기 전에는 이메일과 알림톡을 합쳐 체험 10건까지이고, 충전해도 늘지 않습니다(넘기려면 연동과 충전 모두, notices EMAIL_SHARES_KAKAO_TRIAL). 설정에 따라 체험 공유 대신 가입 초기 하루 받는 사람 상한이 걸릴 수 있습니다(409 J_41_23_1_16, channels[EMAIL].quota) |
| 결과 | GET /campaigns/{uuid}/results 의 channel: "EMAIL", emailSent(보냄), 실패, 링크 클릭. opened 는 링크를 누른 사람 수이고(결과 notes 도 같은 표현), 메일을 읽었는지는 알 수 없어 remindable.unread 가 null 입니다. retryAfterSec 가 숫자면 그 초만큼 기다렸다 다시 조회하세요 |
| 야간·스팸 | 이메일은 광고성이어도 야간에 자동 예약 없이 바로 보냅니다. 이메일 발송 준비 notices 에는 시간과 상관없이 늘 EMAIL_NIGHT_ALLOWED(낮 시간 권장)가 붙습니다. 보내기 전에 스팸 검사를 거칩니다 |
| 원문에 없는 내용 | 이메일 초안의 guide 에 slot.<항목>(사용 방법·조건·대상·절차·기한) 선택 질문(required: false, 단계를 막지 않음)이 나오면, 원문에 없어 본문에서 뺀 내용입니다. 답하면(answer_questions 의 {id: "slot.<항목>", value}, REST 는 PATCH 의 facts) 그 값을 email.confirmedFacts 에 저장하고 그 내용을 넣어 문구 전체를 다시 만듭니다(보낸 곳·수신거부·성격은 유지, 답한 항목은 다시 묻지 않음). 답하지 않아도 보낼 수 있습니다. 다시 만들면 그 전에 직접 고친 문단도 새 문구로 바뀝니다. 이메일 초안에 원문(과 확정한 내용)에 없는 사용 방법·조건이 남으면 경고 INFERRED_VALUE(reason: "EMAIL_INFERRED_PROCEDURE", texts·expressions)가 붙습니다. 사용자에게 확인하고 문구를 고치거나 위 선택 질문에 답하세요(문구를 고치면 경고가 떨어집니다) |
| conversion | 이메일 캠페인의 conversion 은 null 입니다(알림톡 변환 설명이라 이메일에는 쓰지 않음) |
| 이메일이 아직 열리지 않은 스페이스 | 404 J_41_20_1_2. readiness.channels[EMAIL].available 로 미리 확인하세요 |
문자로 보내기
초안을 만들 때 channel: "SMS" 를 넣거나 원문·요청에 '문자로' 같은 말이 있으면 문자 캠페인이 됩니다. 지금 쓸 수 있는지는 GET /readiness 의 channels[SMS] 로 봅니다.
| 항목 | 내용 |
|---|---|
| 범위 | 정보성 안내만. 광고성 원문을 channel: "SMS" 로 주면 400 J_41_21_1_10(data.suggest), 서버가 채널을 정하다 광고로 판정하면 알림톡으로 만들고 channelDecision.reason: "SMS_AD_FALLBACK" |
| 본문 | 원문을 그대로 씁니다((광고) 표기만 지움). 맨 앞에 [스페이스 이름] 이 붙고, 본문은 800바이트(한글 약 400자)까지, 90바이트를 넘으면 LMS. 응답 sms{text, header, fullText, bytes, type, limitBytes, link}, 고칠 때는 PATCH 의 sms: {text}(넘으면 400 J_41_21_1_11, 혜택 표현이 들어가면 400 J_41_21_1_10) |
| 받는 사람 | 명단의 휴대전화 열. 국내 휴대전화만 받고 유선·대표번호는 뺍니다 |
| 켜기 | 자버 전자문서 설정에서 문자 발송을 켜야 합니다. 꺼져 있으면 발송 준비·테스트 발송이 409 J_41_23_1_19(data.reason, data.settingsUrl) |
| 한도 | 발신번호 등록 전: 자버 공용 번호로 스페이스 누적 3건. 등록 뒤: 하루 50명·한 번에 30명·누적 300명. 넘으면 409 J_41_23_1_18(data.code·limit·remaining·resetAt). 카카오 체험 10건·링크·광고 3명은 걸리지 않습니다 |
| 요금 | 건당 50원 선불(summary.estimatedCost = 50 × 대상 수, 모자라면 409 J_41_23_1_14) |
| 야간 | 정보성만이라 예약하지 않고 바로 보냅니다. 밤 9시~아침 8시면 summary.warnings 에 SMS_NIGHT_NOTICE |
| 결과 | GET /campaigns/{uuid}/results 의 channel: "SMS", smsSent(문자 서버에 넘긴 수) |
테스트 발송
POST /campaigns/{uuid}/test-send(MCP send_test, 권한 send:execute)는 확정한 캠페인을 1명에게 실제로 보내 봅니다.
curl -X POST "https://api.jober.io/openApi/v2/agent/campaigns/{uuid}/test-send" \
-H "Authorization: Bearer jbr_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "phone": "01012345678" }'- 받는 곳: 알림톡·문자는
phone, 이메일은email하나만 넣습니다. 비우면 내 연락처(readiness.testSend.prefill)로 보냅니다. 다른 번호·주소도 됩니다(국내 휴대전화·메일 형식만, 틀리면 400J_41_23_1_6). 연락처는 만들지 않습니다. - 한도: 스페이스마다 하루 5번(웹·AI 도구·채널 합산, 한국 시간 0시 초기화). 넘으면 409
J_41_23_1_17(data.resetAt). - 응답
data:{status: "SENDING", documentTaskDefinitionId, channel, to, testQuota{limit, used, remaining, resetAt}}(to는 가운데를 가린 값).testQuota.remaining을 사용자에게 전하세요. - 광고성 알림톡은 밤 9시~아침 8시에 400
J_41_23_1_2, 문자를 켜지 않았으면 409J_41_23_1_19입니다. 발송 내역에는 테스트로 남고 캠페인 대상 수·결과에는 들어가지 않습니다.
미리보기와 만든 페이지 주소
- 캠페인 응답의
explanation{code, text, why}는 서버가 무엇을 어떻게 바꿨는지(text)와 그 이유(why, 광고 표기·안내 페이지로 바꾼 경우만)이고,channelDecision{channel, reason, text}는 고른 채널과 이유입니다. 사용자에게 그대로 보여 주면 됩니다. - 버튼 뒤 페이지를 자버에서 만들었거나 고른 서식이면
campaign.landing.openUrl(작업 조회GET /jobs/{uuid}는result.openUrl)이 만든 페이지를 로그인 없이 보는 공개 미리보기 주소(서명 토큰, 7일)입니다. 쿠폰 사용 화면·안내 페이지·설문을 받는 분이 보는 내용 그대로 보여 줍니다. 자버에서 고치는 편집 주소(로그인 필요)는landing.editUrl·result.editUrl이고,previewUrl은 하위 호환으로 같은 편집 주소입니다. - 내 링크(
LINK)면openUrl은 그 주소, 공유 페이지면 단축 링크입니다. 만드는 중·실패·링크 없음은null입니다. - MCP 는 같은 캠페인 결과에 받는 분 화면 미리보기 이미지(PNG)와
preview.previewUrl을 더 붙입니다(MCP 서버).
응답 코드 모음
응답에 나오는 코드 값을 한곳에 모았습니다. 모두 서버가 정한 값이고, message 가 있으면 사용자에게 그대로 보여 주면 됩니다.
| 위치 | 값 | 뜻 |
|---|---|---|
guide 질문 kind | BALANCE_INSUFFICIENT | 예상 비용이 잔액보다 큼. 충전·요금제·받는 사람 줄이기 선택지 |
guide 질문 kind | EMAIL_SENDER_INFO | 광고 이메일의 보낸 곳 이름·연락처가 필요함(answerType: "senderInfo") |
guide 선택지 value | WAIT_DAILY_RESET | 이메일 가입 초기 하루 상한에 걸림. resetAt(다음 날 0시, 한국 시간) 뒤 이어서 보내기 |
limits[].code | AGENT_EMAIL_NEW_SPACE_DAILY | 가입 초기 스페이스(가입 30일 안이거나 충전한 적 없음)의 이메일 하루 받는 사람 상한(기본 200명, 넘으면 409 J_41_23_1_16). 이 항목의 limit 은 applies: false 여도 수치입니다 |
notices[].code | EMAIL_SHARES_KAKAO_TRIAL | 카카오 채널 연동 전이라 이메일과 알림톡이 체험 10건을 함께 씀. 충전해도 늘지 않고, 더 보내려면 연동과 충전이 모두 필요 |
notices[].code | EMAIL_NIGHT_ALLOWED | 이메일(광고성 포함)은 야간에도 자동 예약 없이 바로 보냄(낮 시간 권장). 이메일 발송 준비에는 늘 붙음 |
warnings[].code | LANDING_NONE_DROPPED | 알림톡에서 링크 없이 보내기(NONE)를 골라 원문 일부가 메시지에 들어가지 않음(texts). 이메일은 이 경고 대신 문구를 다시 만듦 |
warnings[].code | AD_CONTENT_REPLACED | 정보성 빈칸의 쿠폰·홍보 문구를 기본 안내문으로 바꿈(하위 호환 INFERRED_VALUE(AD_CONTENT_IN_INFO)도 함께) |
warnings[].code | EMAIL_CORE_RESTORED | 이메일 문구에서 빠진 원문의 혜택·기간(숫자 표현)을 원문 문장 그대로 되살림(expressions·texts) |
warnings[].code | EMAIL_FACT_NOT_IN_BODY | 확정한 내용(email.confirmedFacts)이 보이는 본문에 없음(slot·texts) |
| 응답 필드 | email.confirmedFacts | 이메일 선택 질문(slot.<항목>)에 답한 내용 |
| guide 선택지 | REDUCE_RECIPIENTS(next.mode: "REPLACE") | SEND_LIMIT_EXCEEDED 의 REDUCE_RECIPIENTS 는 next({tool: "commit_recipients", args: {campaignUuid}, mode: "REPLACE"})대로 줄인 명단으로 commit_recipients 를 다시 부르면 됩니다. 다시 부르면 이번 발송 명단을 바꿉니다(더하지 않음). 이미 저장된 연락처는 그대로 남습니다(context.savedContacts·savedContactsNote). 카카오 채널 연동에는 연동비 50,000원(부가세 별도)과 카카오 심사 3~4일이 듭니다(unlock[].setup: {feeKrw, vat, reviewDays}) |
warnings[].code | KAKAO_CORE_MISSING | 광고 알림톡에서 원문의 금액·혜택(숫자 표현)이 메시지에 없으면 KAKAO_CORE_MISSING 경고(expressions·texts)가 나옵니다. 알림톡 앞단에는 가격·홍보 문구를 넣을 수 없어서 버튼 뒤 페이지에 넣는 것이 기본입니다 |
| readiness 필드 | steps[].targetState | targets 를 줬을 때 NEEDED(목표에 필요)·OK(status 는 그대로) |
| 응답 필드 | variables[].editable·fixedTo | 알림톡의 받는 사람 이름(수신자)·보낸 곳(발신 스페이스) 칸은 발송 때 바뀌는 값이라 고칠 수 없습니다(editable: false, fixedTo: "RECIPIENT_NAME"·"SPACE_NAME", 고치려 하면 400 J_41_21_1_4). 보낸 곳 이름은 자버 스페이스 이름(웹 스페이스 설정)을 따릅니다 |
| guide 선택지 | CHARGE(requiresWith: "CONNECT_CHANNEL") | 체험·3명 한도에서는 충전만으로 풀리지 않고 카카오 채널 연동과 함께 필요 |
| readiness 필드 | onboarding.targetStage | targets 를 줬을 때 TARGET_MET·TARGET_NOT_MET(stage 는 그대로) |
warnings[].code | AD_RECLASSIFIED | 이메일 원문에 혜택 신호가 있거나 정보성 이메일에 광고 표현(expressions)을 넣어 광고 이메일로 바뀜((광고)·수신거부·보낸 곳 정보·수신 동의 확인) |
warnings[].code | INFERRED_VALUE(reason: "AD_CONTENT_IN_INFO") | 정보성 알림톡 추가 메시지에 광고 표현이 있어 기본 안내문으로 바꿈(같은 내용의 AD_CONTENT_REPLACED 와 함께 옴). 그 밖 칸이면 400 J_41_21_1_4(data.reason 같음) |
| readiness 필드 | channels[].capacity | targets(보낼 인원, 1~100000)를 넣으면 channels[].capacity 에 지금 보낼 수 있는 수(now), 모자란 수(shortBy), 충분한지(enough), 해제 방법(unlock[]: CHARGE·CONNECT_KAKAO_CHANNEL·WAIT_DAILY_RESET), 둘 이상 필요한지(needsAllUnlocks)가 나옵니다. basis 는 이 값들의 기준입니다(nature 입력, 없으면 INFO). byNature.INFO 는 정보성 알림톡·이메일 기준, byNature.AD 는 광고 쿠폰·외부 링크 알림톡 기준입니다(카카오 채널 연동 전 3명 상한 포함). 광고 쿠폰 알림톡을 보낼 거라면 byNature.AD 를 보거나 nature: "AD" 를 함께 넣으세요. 근거 값은 byBalance(잔액 ÷ 건당 단가)·byTrial(체험 남은 수)·byDaily(이메일 하루 상한 남은 수)·byLinkAd(링크·광고 3명 남은 수)입니다 |
| readiness 입력 | nature | INFO(기본)·AD. capacity 의 basis 를 정합니다(광고 쿠폰·외부 링크 알림톡이면 AD). REST 는 쿼리 nature |
| readiness 필드 | onboarding.targetStatus·steps[].requiredForTargets | onboarding.targetStatus(targets 를 줬을 때): met(쓸 수 있는 채널로 목표를 채우는지), requiredSteps(kakaoChannel·balance), message, channels[]. 모자라면 steps[].requiredForTargets 가 true 이고 nextStep 이 목표 기준 안내입니다(stage 는 그대로) |
| readiness 필드 | quietHours.channels | 채널별 야간 규칙(KAKAO·EMAIL). 광고성 알림톡만 야간 자동 예약 |
| readiness 필드 | channels[EMAIL].quota | 이메일 한도: kind(KAKAO_TRIAL_SHARED·NEW_SPACE_DAILY·NONE), sharedWithKakao, limit·used·remaining, resetAt, dailyLimit, unlock(배열 [{code, action, url, resetAt}], 없으면 빈 배열), message |
guide 질문 id | slot.<항목>(이메일) | 이메일 원문에 없어 본문에서 뺀 사용 방법·조건·대상·절차·기한을 묻는 선택 질문(kind: "SLOT_VALUE", answerType: "text", required: false, 저장 경고 EMAIL_MISSING_INFO 의 slot). 답하면 confirmedFacts 에 저장하고 문구를 다시 만듦 |
warnings[].code | INFERRED_VALUE(reason: "EMAIL_INFERRED_PROCEDURE") | 이메일 문구에 원문 근거 없는 사용 방법·조건(texts·expressions). 문구를 고치거나 선택 질문에 답하면 떨어짐 |
| 답하기 결과 | replaced[] | 정보성 알림톡 칸 값에 광고 표현이 있어 서버가 바꾼 칸([{id, key, value, reason, expressions}]). 같은 내용이 warnings INFERRED_VALUE(AD_CONTENT_IN_INFO)와 발송 준비 summary.warnings 에 남음 |
| 결과 필드 | smsFallbackDelivered | 문자 대체: 알림톡 전달에 실패해 대신 보낸 문자가 전달된 수(알림톡만, 이메일은 null) |
| readiness 필드 | balanceAsOf | 잔액·한도를 읽은 시각(ISO). 충전 뒤에는 다시 조회하세요 |
| 결과 필드 | retryAfterSec | 숫자면 그 초만큼 기다렸다 결과를 다시 조회. null 이면 더 바뀌지 않음. 이메일은 열람을 알 수 없어 remindable.unread 가 null |
메시지+링크 (버튼에 내 링크)
link.url(초안), landing.link 의 URL 답, POST /campaigns/{uuid}/link 는 같은 기능입니다. 알림톡 버튼을 누르면 내 페이지가 열리고, 자버 웹·발송 API 의 메시지+링크와 같은 방식으로 보냅니다.
- 주소는
http·https공개 주소, 500자 이하입니다. 사설 IP·localhost·.internal·아이디와 비밀번호가 든 주소는 400J_41_22_1_12입니다. - 붙이기 전에 주소와 도메인(쿼리 속 리다이렉트 목적지 포함)을 스팸 기준으로 검사합니다. 막히면 400
J_41_22_1_14(data.reason,data.restrictedKeywords)이고, 검사할 내용이 너무 길면 400J_41_22_1_15입니다. - 버튼 주소가 고정된 알림톡 템플릿에는 다른 주소를 붙일 수 없습니다(400
J_41_22_1_13). - 단축 주소(bit.ly 등)는 막지 않지만 받는 분이 어디로 가는지 알 수 없어 신고될 수 있습니다. 원래 주소를 쓰세요.
- 자버 공유 링크를 넣으면 공유 페이지 방식으로 바꿔 붙이고 응답
convertedTo: "SELECT_SHARE"로 알려 줍니다(열람·응답을 근사치로 셀 수 있어서입니다). - 받는 사람 모두에게 같은 링크로 보냅니다. 결과의 열람(
opened)은 버튼을 누른 수이고(이메일도 링크를 누른 수), 외부 페이지라 응답(responded)은null입니다. - 광고·쿠폰 안내면 경고
LINK_LANDING_NOTICE가 붙습니다. 링크 첫 화면에 받은 혜택 안내가 먼저 보이게 해 주세요.
야간 자동 예약과 수신거부
야간 규칙은 채널별이에요. 광고성 알림톡만 밤 9시~아침 8시(한국 시간)에 보내지 않고, 그 시간에 준비하면 예약 시각 이후 첫 08:00(한국 시간)으로 자동 예약해요(summary.adjustments). 정보성 알림톡과 이메일(광고성 포함)은 야간에도 자동 예약 없이 바로 보내요. 이메일 prepare_send 에는 늘 EMAIL_NIGHT_ALLOWED 안내가 붙어요. 채널별 규칙은 get_send_readiness 의 quietHours.channels 에 있어요.
>
Only advertising Alimtalk sends in KST 21:00-08:00 are auto-scheduled for the first 08:00 KST after the requested time; informational Alimtalk and all emails are sent immediately. Email prepare_send always includes the EMAIL_NIGHT_ALLOWED notice.
- 야간 규칙은 광고성 알림톡에만 걸립니다. 정보성 알림톡과 이메일(광고성 포함)은 야간에도 자동 예약 없이 바로 보냅니다(
quietHours.channels, 이메일EMAIL_NIGHT_ALLOWED). - 광고·혼합·쿠폰 안내 알림톡은 밤 9시부터 아침 8시(한국 시간)에 보낼 수 없습니다. 이 시간에 발송을 준비하면 서버가 묻지 않고 예약 시각 이후 첫 08:00(한국 시간)으로 예약하고. 바뀐 사실은
summary.adjustments에QUIET_HOURS_RESCHEDULED로 알려 주며, 확인값도 바뀐 시각 기준이라 그대로 발송하면 예약됩니다. - 지금 보내기로 준비한 뒤 실행하는 사이 야간이 되면 발송 실행이 400
J_41_23_1_2(data.needsPrepare: true)로 거절됩니다. 다시 준비하면 예약 시각 이후 첫 08:00(한국 시간) 자동 예약으로 바뀝니다. - 옮긴 날짜가 사용기한보다 뒤라 만료된 뒤 도착하게 되면 400
J_41_23_1_11(data.expiryIso,data.scheduledAt)입니다. 사용기한이 보내는 날과 같으면 경고EXPIRY_SAME_DAY가 붙습니다. - 수신거부 링크는 기본으로 붙습니다. 정보성 안내(쿠폰 계열 아님)만
PATCH /campaigns/{uuid}의optOutLink: false로 끌 수 있고, 광고·쿠폰 안내에서 끄려 하면 400J_41_21_1_6(data.reason: "OPT_OUT_REQUIRED")입니다. 수신거부 링크를 붙일 수 없는 템플릿이면 경고OPT_OUT_UNAVAILABLE이 붙습니다.
API 목록
기본 주소 https://api.jober.io/openApi/v2/agent 뒤에 붙입니다. 같은 이름의 MCP 툴과 입력·결과가 같습니다.
| 메서드 | 경로 | MCP 툴 | 권한(scope) |
|---|---|---|---|
| GET | /readiness | get_send_readiness | campaign:read |
| POST | /campaigns | draft_send (Idempotency-Key = 재시도 키) | campaign:write(+링크·생성 survey:write, 명단 contacts:write) |
| GET | /campaigns | list_campaigns | campaign:read |
| GET | /campaigns/{uuid} | get_campaign | campaign:read |
| PATCH | /campaigns/{uuid} | update_campaign (optOutLink·natureChoice·scheduledAt·email·sms 포함) | campaign:write |
| POST | /campaigns/{uuid}/answers | answer_questions | campaign:write(+답마다 링크 survey:write·명단 contacts:write, 없으면 그 답만 skipped) |
| POST | /campaigns/{uuid}/link | set_direct_link ({url}) | survey:write |
| POST | /campaigns/{uuid}/landing | generate_survey ({mode: "GENERATE", needQuestions?}), select_survey ({mode: "SELECT_FRAME" 또는 "SELECT_SHARE" 또는 "NONE", frameId?, shareOptionId?}). 내 링크는 /link | survey:write |
| GET | /jobs/{uuid} | get_job | campaign:read |
| GET | /templates | list_templates | campaign:read |
| GET | /surveys | list_surveys (쿼리 q, limit 1~50) | campaign:read |
| POST | /campaigns/{uuid}/recipients/preview | preview_recipients | contacts:write |
| POST | /campaigns/{uuid}/recipients/commit | commit_recipients | contacts:write |
| POST | /campaigns/{uuid}/test-send | send_test ({phone?, email?}) | send:execute |
| POST | /campaigns/{uuid}/prepare-send | prepare_send | send:execute |
| POST | /campaigns/{uuid}/submit-send | submit_send (Idempotency-Key 필수) | send:execute |
| POST | /campaigns/{uuid}/cancel | cancel_scheduled_send | send:execute |
| GET | /campaigns/{uuid}/results | get_campaign_results (쿼리 explain=true 면 짧은 해설) | campaign:read |
| POST | /campaigns/{uuid}/reminder | prepare_reminder | campaign:write |
오류 코드
| HTTP | errorCode | 뜻과 처리 |
|---|---|---|
| 400 | J_41_20_1_1 | 요청 본문·쿼리 형식이 틀림(data.issues) |
| 400 | J_41_21_1_6 | 답이나 입력 값이 틀림(data.id·expected, 아무것도 적용하지 않음). 광고·쿠폰 안내의 수신거부 끄기는 data.reason: "OPT_OUT_REQUIRED" |
| 409 | J_41_21_4_5 | 같은 Idempotency-Key 로 다른 내용을 보냄. 새 키로 보내세요 |
| 409 | J_41_21_4_2 | revision 이 최신이 아님. 캠페인을 다시 읽고 답하세요. 200 응답의 skipped·failedSteps 에 이 코드와 data.stoppedBy 가 있으면 다른 요청과 부딪혀 남은 단계만 멈춘 것입니다 |
| 400 | J_41_22_1_12 | 링크 주소 형식이 틀렸거나 공개 주소가 아님 |
| 400 | J_41_22_1_13 | 버튼 주소가 고정된 템플릿이라 링크를 붙일 수 없음 |
| 400 | J_41_22_1_14 | 링크가 스팸 판정으로 막힘(data.reason, data.restrictedKeywords). 다른 링크를 넣거나 안내 페이지를 만드세요 |
| 400 | J_41_22_1_15 | 링크와 문구가 길어 검사할 수 없음. 내용을 줄이세요 |
| 409 | J_41_23_1_14 | 잔액이 부족함(data.sourceErrorCode: 연동 J_07_99_1_1·미연동 J_07_99_1_2, data.balance·estimatedCost·shortBy·prepaidUrl·pricingUrl. 발송 실행 시점 거절이면 balance·estimatedCost·shortBy 는 null). prepaidUrl 에서 충전한 뒤 다시 준비하거나 받는 사람을 줄이세요 |
| 400 | J_41_21_1_7 | 채널 값이 틀림(data.expected: ["KAKAO", "EMAIL", "SMS"]) |
| 400 | J_41_21_1_8 | 이 서버에서 문자(SMS)가 아직 꺼져 있음(channels[SMS].reason: "FLAG_OFF"). 이메일이나 알림톡으로 보내세요 |
| 400 | J_41_21_1_10 | 광고성 내용은 문자로 보낼 수 없음(data.nature·suggest·expressions). 알림톡이나 메일로 보내세요 |
| 400 | J_41_21_1_11 | 문자 본문이 비었거나 800바이트를 넘음(data.bytes·limitBytes) |
| 400 | J_41_23_1_6 | 테스트로 받을 번호·메일 형식이 틀림(data.field) |
| 409 | J_41_23_1_17 | 테스트 발송 하루 5번을 다 씀(data.limit·used·remaining·resetAt) |
| 409 | J_41_23_1_18 | 문자 발송 한도를 넘음(data.code: AGENT_SMS_DAILY·AGENT_SMS_PER_SEND·AGENT_SMS_TOTAL·SMS_PUBLIC_SENDER) |
| 409 | J_41_23_1_19 | 자버에서 문자 발송을 켜지 않음(data.reason, data.settingsUrl 을 사용자에게 주세요) |
| 404 | J_41_20_1_2 | 이 스페이스는 아직 이메일 발송이 열려 있지 않음 |
| 409 | J_41_23_1_13 | 채널 미연동(자버 공용 채널)에서 내 링크·광고 쿠폰 안내의 받는 사람이 스페이스 누적 3명을 넘음(data.limit·used·remaining·connectUrl, 준비 거절이면 data.limits·notices). 다시 준비해도 같으니 받는 사람을 줄이거나 채널 연동 |
| 400 | J_41_23_1_10 | 자버가 비상시에 채널 연동을 다시 필수로 켰을 때만 나는 채널 미연동 거절(data.connectUrl). 평소에는 나지 않습니다 |
| 400 | J_41_23_1_2 | 지금 보내기로 준비했는데 실행 시점이 야간(광고성 알림톡만, data.needsPrepare: true, data.nextAllowedAt). 다시 준비하면 예약 시각 이후 첫 08:00(한국 시간)으로 자동 예약 |
| 400 | J_41_23_1_1 | 문구 검사 실패(data.errors[].code: PROMOTIONAL_COPY 는 count·examples 포함, BUTTON_TARGET_FIXED 등). 문구·연락처 칸 값·템플릿을 고친 뒤 다시 준비 |
| 400 | J_41_23_1_11 | 보내는 날이 사용기한보다 뒤(data.expiryIso, data.scheduledAt). 사용기한을 고치세요 |
| 400 | J_41_23_1_12 | Idempotency-Key 헤더가 없거나(발송 실행) 형식이 틀림 |
| 400 | J_41_23_1_8 | 발송 시점의 스팸·금지 표현 검사에 막힘 |
| 409 | J_41_23_4_4 | 아직 보낼 수 없음(링크 주소 없음 등 준비 안 됨). data.reason: "NO_BUTTON_TEMPLATE"(data.landingMode)면 목적지가 있는데 링크 버튼 템플릿이 없음. 링크 없이 보내려면 landing 을 NONE 으로 고른 뒤 다시 준비 |
| 409 | J_41_23_4_6 | 예약 취소 불가(이미 발송 중·완료, data.status). 스팸 검토 예약을 관리자가 처리하는 중이면 data.approvalInProgress: true |
| 409 | J_41_23_4_5 | 접수 결과 확인 중(data.pending). 잠시 뒤 같은 확인값·같은 키로 다시 |
| 503 | J_41_23_2_3 | 카카오 채널 상태를 잠시 확인하지 못함(발송 준비·발송 실행). data.needsPrepare 는 붙지 않습니다. 잠시 뒤 같은 요청을 다시 보내세요. 발송 실행은 확정 전에 멈춰 준비가 그대로이니 같은 확인값으로 다시 보내면 됩니다(Idempotency-Key 는 같은 값을 다시 써도, 새로 만들어도 안전합니다) |
| 429 | J_41_20_4_2 | 내용 검사(변환·링크 검사·발송 준비)는 스페이스마다 1분에 30번까지 |
| 404 | J_41_25_4_7 | 없는 경로·메서드 |
잔액 부족은 409 J_41_23_1_14 로 따로 옵니다(위 표). 그 밖의 발송 거절(J_41_23_1_9 연동비·요금제·체험 한도, J_41_23_4_1·J_41_23_4_2 다시 준비 등)은 MCP 오류와 문제 해결과 같습니다. 체험 한도 거절(J_41_23_1_9)도 준비 단계면 data.limits·data.notices 가 함께 옵니다. 지금은 내지 않는 J_41_23_1_4(링크 버튼 + 채널 미연동)는 J_41_23_1_13 3명 상한으로 바뀌었습니다.
한도
| 항목 | 값 |
|---|---|
| 원문 길이 | 최대 4,000자 |
| 명단 | 요청당 최대 5,000행 |
| 한 번에 보내는 답 | 최대 30개 |
Idempotency-Key | 8~80자, 영문·숫자·_.:-(헤더로만) |
| 호출 수 | 연결(키 또는 OAuth 연결)마다 1분에 120번 |
| 확인값 유효 시간 | 10분 |
| 내 링크 | 캠페인당 1개, 500자 이하 |
| AI 안내 페이지·설문 생성 | 스페이스마다 하루 50개 |
| 채널 미연동 카카오 발송 | 스페이스당 10건(삭제한 문서 포함, 면제 고객 제외). 채널 미연동 발송과 한도 |
| 채널 미연동 내 링크·광고 쿠폰 안내 | 받는 사람 스페이스 누적 3명(면제 고객 포함) |
| 회사 API 키 | 스페이스당 활성 5개 |
| 개인 API 키 | 사람마다 스페이스당 활성 20개 |
| 요금 | 웹에서 보낼 때와 같은 기준으로 스페이스에서 차감. 요금제 참고 |
자주 묻는 질문
발송 API 와 무엇이 다른가요?
발송 API 는 웹에서 미리 만든 빠른 발송 세트로 보내고, 자유 발송 API 는 세트 없이 보낼 원문을 보내면 서버가 알림톡·이메일·문자 캠페인으로 바꿔 보냅니다. 같은 회사 API 키에 두 API 의 권한을 함께 줄 수 있습니다(발송 API 는 x-jober-openapi 헤더, 자유 발송 API 는 Authorization: Bearer).
카카오 채널을 연동하지 않아도 보낼 수 있나요?
네. 자버 공용 채널로 웹 발송과 같은 한도(체험 10건 등) 안에서 보냅니다. 다만 내 링크를 붙이거나 광고 쿠폰을 안내하는 발송은 받는 사람 스페이스 누적 3명까지이고, 넘으면 409 J_41_23_1_13 입니다. 남은 수는 GET /readiness 의 limits 에 있습니다. 제한 없이 보내려면 카카오 채널을 연동하세요.
광고 문자를 그대로 넣어도 되나요?
넣어도 됩니다. 광고인지는 묻지 않고 서버가 정합니다. 알림톡 앞에는 받은 혜택(쿠폰명·사용기한 등)을 담담하게 안내하는 문구만 남기고, 할인·이벤트 같은 홍보 내용은 버튼 뒤 페이지로 옮깁니다. 무엇을 바꿨는지는 응답의 explanation(한 줄 설명과 이유)과 conversion 에 있습니다. 광고성 내용은 문자로는 보낼 수 없습니다. 주류·대출처럼 보낼 수 없는 내용은 막힙니다.
밤에 발송을 요청하면 어떻게 되나요?
광고성 알림톡(광고·쿠폰 안내)은 묻지 않고 예약 시각 이후 첫 08:00(한국 시간)으로 예약됩니다. 정보성 알림톡과 이메일(광고성 포함)은 야간에도 자동 예약 없이 바로 보냅니다. 발송 준비 응답의 summary.adjustments 에 바뀐 시각이 있고, 그대로 발송하면 예약으로 접수됩니다. 예약은 취소할 수 있습니다.
MCP 용으로 받은 OAuth 토큰을 써도 되나요?
안 됩니다. OAuth 토큰은 사용 대상(resource)별로 발급되므로 이 API 용(https://api.jober.io/openApi/v2/agent)으로 따로 받아야 합니다. 개인 API 키는 MCP 와 이 API 에 같이 쓸 수 있고, 회사 API 키는 이 API 에만 쓸 수 있습니다.