# 빠른 시작 (/docs/call-batches/quickstart)

## 준비물

- **API Key** 와 **Account ID** — 콘솔 > 개발자 > API 키
- 계정에 등록된 **발신 번호**
- 발신 시 실행할 **대상** — `CallFlowId` · `AgentId` · `Url` 중 하나
- 계정의 **배치 발신 활성화** (미활성 시 생성이 `403` 입니다)

모든 요청은 `Authorization: Bearer <api_key>` 헤더로 인증하며, 기본 주소는 `https://api.claw-ops.com` 입니다.

**배치 생성**

명단을 담아 배치를 만듭니다. 필수값은 `Name` · `From` · `Tasks` 입니다.

```bash
curl -X POST https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/call-batches \
  -H "Authorization: Bearer $CLAWOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "7월 예약 확인",
    "From": "07052358010",
    "CallFlowId": "cf_9a8b7c6d",
    "MaxConcurrency": 3,
    "PacingPerMinute": 20,
    "Rounds": [
      { "Windows": [{ "Days": [1, 2, 3, 4, 5], "Start": "09:00", "End": "18:00" }] }
    ],
    "Tasks": [
      { "To": "01012345678", "Variables": { "customer_name": "홍길동", "visit_date": "7월 30일" } },
      { "To": "01087654321", "Variables": { "customer_name": "김영희", "visit_date": "7월 31일" } }
    ]
  }'
```

응답으로 배치 정보가 돌아옵니다. `batchId` 를 이후 요청에 사용합니다.

```json
{
  "batchId": "clx1a2b3c4d5e",
  "name": "7월 예약 확인",
  "status": "running",
  "from": "07052358010",
  "callFlowId": "cf_9a8b7c6d",
  "maxConcurrency": 3,
  "pacingPerMinute": 20,
  "timezone": "Asia/Seoul",
  "counts": { "pending": 2 }
}
```


기본 상태는 `running` — 만들면 바로 발신 대기입니다. 명단을 먼저 확인하고 싶다면
`"Status": "paused"` 로 만든 뒤, 준비되면 `resume` 하세요.

**안 받으면 다시 걸고, 끝내 안 되면 문자 남기기**

여기까지가 기본형이고, 실무에서는 대개 세 가지를 함께 켭니다 — **자동응답기 감지**로 누가
받았는지 가리고, **다시 걸기**로 안 받은 분께 재발신하고, 그래도 안 되면 **문자**를 남깁니다.
셋 다 배치 생성 한 번에 들어갑니다.

```bash
curl -X POST https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/call-batches \
  -H "Authorization: Bearer $CLAWOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "Name": "7월 예약 확인",
    "From": "07052358010",
    "CallFlowId": "cf_9a8b7c6d",
    "MaxConcurrency": 3,
    "PacingPerMinute": 20,
    "MachineDetection": "Enable",
    "Rounds": [
      { "Windows": [{ "Days": [1,2,3,4,5], "Start": "10:00", "End": "18:00" }] },
      { "After": 30, "On": ["no-answer", "busy"],
        "Windows": [{ "Days": [1,2,3,4,5], "Start": "18:00", "End": "21:00" }] },
      { "After": 45, "On": ["no-answer", "busy"],
        "Windows": [{ "Days": [1,2,3,4,5], "Start": "18:00", "End": "21:00" }] }
    ],
    "MessagePolicy": {
      "OnFinalFail": {
        "Body": "{{customer_name}}님, {{visit_date}} 예약 확인차 전화드렸는데 연결이 어려워 문자 남깁니다."
      }
    },
    "Tasks": [
      { "To": "01012345678", "Variables": { "customer_name": "홍길동", "visit_date": "7월 30일" } }
    ]
  }'
```

이 배치에서 한 수신자가 겪는 일은 이렇습니다.

| 시점 | 일어나는 일 |
| --- | --- |
| 1차 통화 (낮 10~18시) | 안 받음 → `dialRound: 1`, 30분 뒤로 재예약 |
| 30분 뒤가 아직 낮이면 | 2차는 저녁 차수라 **18시까지 기다립니다** |
| 2차 통화 (18~21시) | 또 안 받음 → `dialRound: 2`, 45분 뒤로 재예약 |
| 3차 통화 (18~21시) | 또 안 받음 → 남은 차수 없음, `status: "failed"` |
| 그 직후 | `OnFinalFail` 문자 발송 |

정책상의 재시도 시각이 그 차수의 시간대 밖이면 **시간대가 열릴 때까지 기다렸다가** 겁니다.
화면과 API 의 `expectedDialAt` 이 그 보정을 반영한 값입니다(`nextAttemptAt` 은 보정 전).

통화가 **연결된 분께는 문자가 가지 않습니다.** `OnFirstFail` 을 함께 주면 1차 실패 직후에도
한 통 나가므로, 끝내 안 받은 분은 총 2통을 받습니다.

`On` 의 **`machine` 은 `MachineDetection` 을 함께 켠 배치에서만 동작합니다.** 감지를 끈 채로
넣으면 요청이 거절되지는 않지만 아무 일도 일어나지 않습니다 — 맞출 판정값(`answeredBy`)이
아예 없기 때문입니다.

`MachineDetection` 은 **발신할 때마다** 애드온 활성 여부를 다시 확인합니다. 배치를 만든 뒤
애드온을 해지하면 남은 통화는 감지 없이 걸립니다(배치가 실패하지는 않습니다).

`OnFirstFail` 은 **재시도 차수가 있어야**(`Rounds` 가 2개 이상) 씁니다. 다시 걸지 않으면 첫 실패가
곧 최종 실패라 `OnFinalFail` 과 같은 뜻이 되기 때문이며, 함께 주지 않으면
`message_policy_invalid` 400 입니다.

문자는 통화가 실패한 **직후** 나갑니다 — 차수 시간대를 따로 기다리지 않습니다. 저녁 차수로
설정한 배치라도 낮에 실패한 통화의 문자는 낮에 갑니다(법정 금지 시간 21~08시에 걸리면
다음 날 아침으로 밀립니다).

생성 응답의 `warnings` 를 꼭 확인하세요. 거절할 정도는 아니지만 **시작한 뒤에는 손쓸 수 없는**
것들이 여기 담깁니다.

```json
{
  "batchId": "clx1a2b3c4d5e",
  "status": "running",
  "machineDetection": "Enable",
  "retryPolicy": { "delaysMinutes": [30, 180], "retryOn": ["no-answer", "busy"] },
  "messagePolicy": { "onFinalFail": { "body": "{{customer_name}}님, ..." } },
  "warnings": [
    "07052358010 번호로 문자가 전송된 이력이 없습니다. 문자 발신번호 등록은 음성과 별개 절차라, 등록되지 않은 번호는 발송 요청은 성공해도 통신사에서 전량 거절될 수 있습니다."
  ],
  "counts": { "pending": 1 }
}
```

문자가 실제로 도달했는지는 [결과 조회](/docs/call-batches/results)의 `messageDeliveryStatus`
로 확인합니다. `messageStatus: "queued"` 는 통신사에 넘겼다는 뜻일 뿐 전달 성공이 아닙니다.

**수신자별 값 넣기**

수신자마다 다른 값을 쓰려면 `Tasks[].Variables` 에 넣습니다.
콜 플로우에서는 `{{customer_name}}` 처럼 이중 중괄호로 참조합니다.

`AgentId` 배치에서는 같은 값이 그 통화의 [CallContext](/docs/webhooks/call-context) `Variables` 로
에이전트에 전달됩니다. 에이전트 지시문의 `{{customer_name}}` 도 그 값으로 바뀝니다.

변수 **이름**은 영문·숫자·밑줄만 가능합니다. 값에는 한글을 써도 됩니다.

**명단 나눠 넣기**

명단이 크거나 나중에 추가해야 하면 별도 요청으로 더할 수 있습니다.

```bash
curl -X POST https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/call-batches/clx1a2b3c4d5e/tasks \
  -H "Authorization: Bearer $CLAWOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "Tasks": [ { "To": "01099998888", "Variables": { "customer_name": "이철수" } } ] }'
```

```json
{ "added": 1 }
```


**건수 제한은 없습니다.** 상한은 요청 본문 크기(10MB)이며, 3만 건 규모까지 한 번에 넣을 수 있습니다.
초과하면 `413` 이 반환되므로 그럴 땐 나눠서 요청하세요. 이미 종료된 배치에는 추가할 수 없습니다(`409`).

**일시정지 · 재개 · 취소**

세 동작 모두 같은 엔드포인트에서 `Action` 값으로 구분합니다.

```bash
curl -X POST https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/call-batches/clx1a2b3c4d5e/actions \
  -H "Authorization: Bearer $CLAWOPS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "Action": "pause" }'
```

| Action | 동작 |
| --- | --- |
| `pause` | 신규 발신만 중단합니다. **진행 중인 통화는 그대로 유지**됩니다 |
| `resume` | 다시 발신을 시작합니다 |
| `cancel` | 남은 대상을 모두 취소합니다. 이미 걸린 통화는 유지되며, 개별 종료가 필요하면 [통화 제어 API](/docs/api/calls/updateCall) 를 사용하세요 |

**진행 상황 확인**

배치 상세 조회의 `counts` 가 상태별 수신자 수입니다.

```bash
curl https://api.claw-ops.com/v1/accounts/AC1a2b3c4d/call-batches/clx1a2b3c4d5e \
  -H "Authorization: Bearer $CLAWOPS_API_KEY"
```

```json
{
  "batchId": "clx1a2b3c4d5e",
  "status": "running",
  "counts": { "pending": 120, "dialing": 3, "done": 77 }
}
```

계정의 배치 목록은 `GET /v1/accounts/{accountId}/call-batches` 로 조회합니다.
수신자별 결과는 [결과 조회](/docs/call-batches/results) 를 참고하세요.

## 주요 파라미터

- `Name` (string, 필수): 배치 이름
- `From` (string, 필수): 발신 번호 — 계정에 등록된 번호여야 합니다
- `Tasks` (array, 필수): 수신자 목록. 각 항목은 To(필수)와 Variables(선택)
- `CallFlowId / AgentId / Url` (string, 필수): 발신 대상. 셋 중 정확히 하나
- `Status` (string, 선택): 생성 직후 상태. running(기본) 또는 paused
- `MaxConcurrency` (integer, 선택): 이 배치가 동시에 유지할 통화 수. 기본 1
- `PacingPerMinute` (integer, 선택): 분당 최대 발신 수. 기본 10
- `Timezone` (string, 선택): 발신 시간대 판정 기준. 기본 Asia/Seoul
- `Rounds` (array, 선택): 발신 차수(최대 5). 배열 인덱스가 곧 차수이고 Rounds[0]이 1차. 각 항목은 After(분, 2차부터 필수 · 1~1440) · On(직전 결과 조건, 2차부터 필수) · Windows(이 차수의 시간대. Days 1=월~7=일 · Start · End). 생략하면 한 번만 걸고 시간대는 법정 허용 시간 전체
- `StartAt` (string, 선택): 이 시각 이후부터 발신 시작 (ISO 8601)
- `EndAt` (string, 선택): 이 시각이 지나면 남은 대상은 발신하지 않고 종료 (ISO 8601)
- `Priority` (integer, 선택): 같은 계정에 배치가 여럿일 때 우선순위(높을수록 먼저). 기본 0
- `MachineDetection` (string, 선택): 자동응답기 감지. None(기본·사용 안 함) · Enable(감지만) · Hangup(자동응답기면 끊기). AMD 부가서비스가 필요하고 감지한 통화마다 요금이 붙습니다
- `MessagePolicy` (object, 선택): 통화가 안 된 분께 보낼 문자. OnFirstFail(첫 실패 직후 · 재시도 차수 필요) · OnFinalFail(끝내 미연결). 각각 { Body } 이며 본문은 2000자 이하, {{변수}}는 그 수신자의 Variables 로 치환됩니다

### 자동응답기 감지 (선택)

`MachineDetection` 을 주면 사람이 받았는지 자동응답기(음성사서함)인지 판별합니다. 기본은 **사용 안 함**입니다.

| 값 | 동작 |
| --- | --- |
| `None` (기본) | 판별하지 않습니다 |
| `Enable` | 판별만 하고 통화는 그대로 진행합니다. 결과의 `answeredBy` 에 `human` · `machine` · `unknown` 이 담깁니다 |
| `Hangup` | 자동응답기로 판별되면 통화를 끊습니다 |

AMD 부가서비스가 활성화된 계정만 쓸 수 있습니다(없으면 `amd_addon_disabled` 422). 판별한 통화마다 요금이 붙으므로, 결과를 실제로 활용할 때만 켜세요. 판별이 항상 성공하지는 않아 `unknown` 이 나올 수 있습니다.

## 오류 코드

| 코드 | 상태 | 원인 |
| --- | --- | --- |
| `batch_disabled` | 403 | 배치 발신이 활성화되지 않은 계정 |
| `missing_field` | 400 | `Name` 또는 `From` 누락 |
| `from_not_registered` | 400 | `From` 이 계정에 등록된 번호가 아님 |
| `target_invalid` | 400 | `CallFlowId` · `AgentId` · `Url` 중 하나만 지정해야 함 |
| `call_flow_not_found` | 404 | `CallFlowId` 에 해당하는 콜 플로우 없음 |
| `agent_not_found` | 404 | `AgentId` 에 해당하는 에이전트 없음 |
| `tasks_required` | 400 | `Tasks` 가 비어 있음 |
| `task_to_required` | 400 | `Tasks[].To` 누락 |
| `task_to_invalid` | 400 | 수신 번호 형식 오류 |
| `task_duplicate` | 400 | 같은 요청 안에 중복 번호 |
| `rounds_invalid` | 400 | `Rounds` 형식 오류 — 개수(1~5) · 1차에 `After`/`On` 사용 · 2차부터 `After`(1~1440)/`On` 누락 · `Windows` 의 `Days` 범위나 `HH:MM` 형식 · 법정 허용 시간(08:00~21:00)과 겹치는 구간 없음 |
| `timezone_invalid` | 400 | 알 수 없는 시간대 |
| `start_at_invalid` · `end_at_invalid` | 400 | 시각 형식 오류 또는 `EndAt` 이 `StartAt` 보다 앞섬 |
| `status_invalid` | 400 | `Status` 는 `running` · `paused` 만 가능 |
| `machine_detection_invalid` | 400 | `MachineDetection` 은 `None` · `Enable` · `Hangup` 만 가능 |
| `amd_addon_disabled` | 422 | AMD 부가서비스가 비활성 상태에서 감지를 요청 |
| `message_policy_invalid` | 400 | `Body` 누락·2000자 초과 · 본문의 `{{변수}}` 가 없는 수신자 존재 · 재시도 차수 없이 `OnFirstFail` 사용 |
| `batch_not_found` | 404 | 배치 없음 |
| `batch_closed` | 409 | 이미 종료된 배치에 대한 추가·제어 |
