# 요청

요청은 하나의 모델을 한 번 호출하는 것입니다. 요청에는 id(`req_…`), 상태, API 크레딧 기준 비용(1000 = 1 USD)이 있으며, 요청이
성공하면 생성된 파일도 포함됩니다. 아래의 모든 엔드포인트에서 모든 모델에 대해 동일한 객체가 반환됩니다.

## 모델 찾기

1,496개 모델의 전체 카탈로그는 공개 JSON입니다(API 키 불필요).

```bash
curl "https://api.doitong.com/v1/models?category=text-to-video&lab=Google&limit=20"
```

| 쿼리 매개변수 | 의미 |
|---|---|
| `q` | 이름, id, 제작사 또는 태그에 모두 포함되어야 하는 단어입니다. 예: `kling 3`. |
| `category` | 카테고리 id 하나입니다. 예: `text-to-video` 또는 `upscale`. `GET https://api.doitong.com/v1/categories`에서 개수와 함께 전체 21개를 확인할 수 있습니다. |
| `lab` | 모델 제작사입니다. 이름 또는 id로 지정합니다. 예: `Google` 또는 `black-forest-labs`. |
| `family` | 이전 행의 `family` id입니다. 하나의 모델에 속하는 모든 변형을 나타냅니다(pro, fast, image-to-video, …). |
| `mode` | `exact`(입력으로 가격 계산), `measured`(입력 파일의 길이 또는 크기로 계산), `estimate`(실행당 예상 가격) 또는 `metered`(실제 사용량에 따라 실행당 최대 금액까지 청구)입니다. |
| `limit`, `cursor` | 페이지 크기(1~100, 기본값 50)와 이전 페이지의 `next_cursor`입니다. |

각 행에는 `id`, `name`, `lab`, `category`, `family`, `summary`, `tags`, `output_kind`, `docs_url` 및 `price` 객체가 있습니다([가격 책정](https://doitong.com/developers/docs/pricing#price-objects) 참조). `GET https://api.doitong.com/v1/models/{model_id}`를 사용하면 전체 입력 및 출력 JSON Schema, 표준 매개변수 이름, 가격 규칙 및 예시 입력이 추가로 반환됩니다. 알 수 없는
`category` 또는 `mode`는 `400 invalid_parameter`를 반환합니다.

## 모델 실행

```bash
curl -X POST "https://api.doitong.com/v1/run/{model_id}?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1842-cover" \
  -d '{"prompt": "..."}'
```

- 본문은 JSON 객체 형식의 모델 입력입니다. 각 모델 페이지에 매개변수가 나와 있습니다.
- 입력은 비용이 청구되기 전에 확인됩니다. 잘못된 입력은 문제가 있는 `param`과 함께 `422 invalid_input`을 반환하며 비용이 청구되지 않습니다.
- `?wait=N`(최대 20초)은 요청이 완료되거나 시간이 만료될 때까지 호출을 유지합니다.
- `?webhook_url=https://…`은 요청이 완료되면 해당 URL로 서명된 POST를 전송합니다([웹훅](https://doitong.com/developers/docs/webhooks) 참조).
- `?storage=permanent` 또는 `?storage=temporary`는 출력 파일을 얼마나 오래 보관할지 지정합니다. 지정하지 않으면 계정 설정이 적용됩니다([파일 보관 위치](#storage) 참조).

요청이 아직 처리 중이면 응답은 `202 Accepted`이고, 대기 시간 내에 완료되면 `200 OK`입니다(또는 아래 설명하는 재생 요청인 경우). `Location` 및 `Doitong-Request-Id` 헤더에 요청 URL과 id가 들어 있습니다.

## 상태

요청 수명 주기: `queued` → `running` → `succeeded` | `failed` | `canceled` (최종 상태이며, 실패 및 취소된 요청은 환불됩니다).

| 상태 | 의미 | 최종 상태 |
|---|---|---|
| `queued` | 승인되고 비용이 청구되었으며 작업자를 기다리는 중입니다. `queue_position`에 대기열 순서가 표시될 수 있습니다. | 아니요 |
| `running` | 모델이 생성 중입니다. | 아니요 |
| `succeeded` | 완료되었습니다. `outputs`에 파일 목록이 들어 있습니다. | 예 |
| `failed` | 모델 또는 제한으로 인해 중지되었습니다. `error`에 이유가 표시됩니다. API 크레딧은 환불됩니다. | 예 |
| `canceled` | 결과를 생성하기 전에 취소되었습니다. API 크레딧은 환불됩니다. | 예 |

시간 제한보다 오래 실행된 요청은 `timeout` 코드로 실패하고 환불됩니다. 이미지에는 15분, 동영상에는
60분, 오디오 및 기타 항목에는 30분이 적용됩니다.

## 요청 객체

```json
{
  "id": "req_8k2m4q7x1z9c3v5b6n0p2r4t",
  "object": "request",
  "model": "flux/schnell",
  "status": "succeeded",
  "created_at": "2026-10-20T12:00:00Z",
  "started_at": "2026-10-20T12:00:01Z",
  "completed_at": "2026-10-20T12:00:02Z",
  "queue_position": null,
  "cost": { "credits": 3, "usd": "0.003", "charged": 3, "refunded": 0 },
  "input": { "prompt": "..." },
  "output": { "images": [ { "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "width": 1024, "height": 768 } ] },
  "outputs": [
    { "type": "image", "url": "https://content.doitong.com/dev-api/…/0-output.jpeg", "content_type": "image/jpeg",
      "width": 1024, "height": 768, "file_size": 183920, "stored": true }
  ],
  "error": null,
  "metrics": { "inference_seconds": 1.2 },
  "storage": { "mode": "permanent", "bytes": 183920, "expires_at": null, "expired": false },
  "urls": {
    "self": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t",
    "cancel": "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel"
  }
}
```

- `outputs`는 모든 모델에서 동일합니다. 생성된 파일마다 하나의 항목이 있으며 `type`(`image`, `video`, `audio`,
  `file` 또는 `3d`), 스토리지의 `url`, `content_type` 및 확인 가능한 경우 `file_size`, `width`, `height`,
  `duration`이 포함됩니다.
- `output`은 모델 자체의 결과 객체입니다(필드는 각 모델 페이지에 나열됨). 동일한 URL이 포함됩니다.
- `storage`는 파일 보관 방식을 나타냅니다. `mode`(`permanent` 또는 `temporary`), 저장된 `bytes`, `expires_at`(임시 파일이 삭제되는 시점이며 영구 파일은 `null`) 및 `expired`(파일이 삭제되면 `true`)가 포함됩니다.
- `cost.credits`는 API 크레딧 기준 호출 가격이며(`cost.usd`는 USD 기준), `cost.charged`는 API 잔액에서 차감된 금액, `cost.refunded`는 환불된 금액입니다. `metered` 모델에서는 `cost.credits`가 시작 시 보류된 최대 금액입니다(`cost.held`). 실행이 성공하면 `cost.settled`에 실제 사용량이 표시되고 최대 금액과의 차액은
  `cost.refunded`에 들어갑니다([사용량에 따른 청구](https://doitong.com/developers/docs/pricing#metered) 참조).
- 요청이 실패하면 `error`는 `{ "type": …, "code": …, "message": … }` 형식입니다.

## 요청 조회

```bash
curl "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t?wait=20" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

- `?wait=N`(최대 20초)은 롱 폴링을 수행합니다. 요청이 최종 상태가 되면 즉시 반환되며, 그렇지 않으면 N초 후 현재 상태를 반환합니다. 결과를 기다리는 가장 간단한 방법입니다.
- `?logs=1`을 지정하면 모델 진행 로그 줄(최근 50개)이 `logs`로 추가됩니다.

자신의 계정에 속한 요청만 조회할 수 있습니다. 다른 id를 사용하면 `404 request_not_found`가 반환됩니다.

## 요청 목록 조회

```bash
curl "https://api.doitong.com/v1/requests?limit=20&status=succeeded&model=flux/schnell" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

최신 항목부터 표시됩니다. `limit`은 1~100(기본값 20)이며, `status`와 `model`로 필터링할 수 있습니다. 응답은
`{ "object": "list", "data": [ … ], "has_more": true, "next_cursor": "req_…" }` 형식입니다. 다음 페이지를 가져오려면
`cursor=<next_cursor>`를 전달합니다.

## 요청 취소

```bash
curl -X POST "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/cancel" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

- 모델이 시작되기 전에 취소하면 상태가 `canceled`가 되고 API 크레딧이 환불되며 `200`이 반환됩니다.
- 이미 생성 중이면 취소 요청이 전달되고 `"cancel_requested": true`와 함께 `202`가 반환됩니다. 모델이 계속 실행되어 완료되면 요청은 정상적으로 성공하고 비용이 청구됩니다.
- 이미 최종 상태이면 `409 not_cancelable`입니다(이미 `canceled`인 요청은 `200`을 반환합니다).

## 요청의 파일 삭제

```bash
curl -X DELETE "https://api.doitong.com/v1/requests/req_8k2m4q7x1z9c3v5b6n0p2r4t/files" \
  -H "Authorization: Bearer $DOITONG_API_KEY"
```

요청의 출력 파일 사본을 스토리지에서 즉시 삭제하고 스토리지 요금 청구를 중지합니다. 응답은
`storage.expired: true`가 포함된 요청 객체입니다. 레코드에 URL은 남지만 더 이상 작동하지 않습니다. 다시 호출해도 아무 변경이 없습니다
(`200`). 아직 `queued` 또는 `running` 상태인 요청에는 파일이 없으므로 `409 request_active`가 반환됩니다. `models:run`이 필요합니다.

## 멱등성: 이중 청구 없이 재시도

네트워크는 실패할 수 있습니다. 안전하게 재시도하려면 `Idempotency-Key` 헤더를 전송합니다(최대 255자의 문자열, 작업마다 고유해야 하며, 예를 들어 자체 주문 id를 사용할 수 있음).

- 이전과 동일한 키와 동일한 본문: 원래 요청이 반환됩니다(`200`, 헤더
  `Idempotent-Replayed: true`). 다시 비용이 청구되지 않습니다.
- 모델에 도달하기 전에 실패한 실행 후 동일한 키와 본문을 사용한 경우(`upstream_error` 또는 `internal_error`가 제출 시 발생하거나, `insufficient_credits`, `budget_exceeded`, `too_many_inflight`인 경우): 생성된 것이 없고 비용도 청구되지 않으므로 실패를 반환하는 대신 재시도 시 모델이 다시 실행됩니다.
- 동일한 키와 다른 본문: `409 idempotency_mismatch`입니다.
- 키의 범위는 해당 키를 전송한 API 키로 제한됩니다.

## 파일 보관 위치

모든 출력 파일은 요청이 `succeeded`로 표시되기 전에 스토리지에 복사되므로 `outputs`의 URL은 자체 호스트를 가리키며 직접 링크할 수 있습니다. 파일을 얼마나 오래 보관할지는 직접 선택합니다.

| 모드 | 보관 기간 | 가격 |
|---|---|---|
| `permanent` (기본값) | 삭제할 때까지 | 월별 GB당 100 API 크레딧($0.10), API 잔액에서 매일 청구 |
| `temporary` | 7일 후 삭제 | 무료 |

- 새 요청에는 계정 설정이 적용됩니다. [대시보드](https://doitong.com/developers/dashboard#balance)의 "파일 영구 보관" 스위치를 사용하거나
  `PATCH https://api.doitong.com/v1/account/storage`에 `{"mode": "temporary"}`를 전달할 수 있습니다. `GET https://api.doitong.com/v1/account`의 `storage`에 설정과 보관 중인 바이트 및 월별 예상 금액이 표시됩니다.
- 실행 시 `?storage=permanent|temporary`를 지정하면 해당 실행에 한해 설정을 덮어씁니다(MCP `run` 도구의 `storage` 인수).
- 설정을 변경해도 이미 보관 중인 파일에는 영향을 주지 않습니다. 해당 파일은
  [`DELETE /requests/{id}/files`](#delete-files)로 삭제합니다.
- API 잔액으로 스토리지 비용을 지불할 수 없으면 비용이 청구되지 않고 이메일이 발송됩니다. 30일 이내에 충전하지 않으면 보관 중인 파일이 임시 파일로 전환되고 7일 후 삭제됩니다.

드물게 자체 사본 생성에 실패하면 항목에 `"stored": false`가 표시되고 URL은 원본의 짧은 유효 기간 URL이 됩니다.
즉시 다운로드하세요.
