> ## Documentation Index
> Fetch the complete documentation index at: https://make.edicus.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 오류 처리

> 상태 코드와 error.code로 실패를 판정하는 방법

`api.edicus.io`는 실패를 **4xx와 5xx 상태 코드**로 알리고, body는 언제나 같은 모양입니다. 클라이언트는 상태 코드로 실패를 알아채고 `error.code`로 원인을 구분합니다. 리소스 호스트는 방식이 다르므로 따로 봅니다.

## 오류 body

```json theme={null}
{
  "error": {
    "code": "project_not_found",
    "message": "프로젝트가 없습니다",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

| 필드 | 설명 |
| - | - |
| `code` | snake\_case 오류 코드입니다. 이 값으로 분기합니다 |
| `message` | 사람이 읽는 설명입니다. 바뀔 수 있으니 파싱하지 않습니다 |
| `details` | 선택입니다. 검증 실패면 `issues`에 어긋난 항목 목록이, 상태 충돌이면 현재 상태가 들어 있습니다 |
| `request_id` | 요청 id입니다. 응답 헤더 `x-request-id`에도 같은 값이 있습니다. 문의할 때 함께 알려 주세요 |

"요청을 처리하지 못했다"는 4xx와 5xx이고, "처리했지만 답이 아니오다"는 200입니다. 예를 들어 `can_order: false`, 프리뷰 URL 여러 개 조회의 항목별 `error`는 200 안에 담겨 옵니다.

확인을 한 곳에 모아 두는 방법은 [Server API 호출하기](/docs/server/client#래퍼-하나-만들기)에 있습니다.

## 상태 코드별 `error.code`

| 상태 | `error.code` | 무슨 뜻인가 | 어떻게 하나 |
| - | - | - | - |
| `400` | `invalid_request` | 헤더, 경로, body가 규칙에 맞지 않습니다. `details.issues`에 어긋난 항목이 있습니다 | `edicus-uid` 형식, 필수 필드, 길이 제한을 확인하세요 |
| `401` | `no_credentials` | 자격 증명 헤더가 없습니다 | `edicus-api-key` 헤더를 붙이세요 |
| `401` | `invalid_api_key` | API 키가 틀렸습니다 | `edicus-api-key` 값을 확인하세요 |
| `403` | `api_key_required` | 사용자 토큰 발급을 API 키가 아닌 자격 증명으로 호출했습니다 | `edicus-api-key`로 호출하세요 |
| `403` | `oem_not_allowed` | `oem-partner-code`의 파트너를 대행할 권한이 없습니다 | OEM 관계를 확인하세요 |
| `403` | `order_not_owned` | `edicus-uid` 사용자의 주문이 아닙니다 | 주문과 uid의 짝을 확인하세요 |
| `404` | `project_not_found` | 프로젝트가 없거나 그 사용자의 프로젝트가 아닙니다 | `project_id`와 `edicus-uid`를 확인하세요 |
| `404` | `order_not_found` | 주문이 없거나 이 파트너의 주문이 아닙니다 | `order_id`를 확인하세요 |
| `404` | `route_not_found` | 존재하지 않는 경로입니다 | 경로와 메서드를 확인하세요 |
| `409` | `project_not_editing` | `editing`이 아니라 처리할 수 없습니다. 잠정 주문에서는 `details.status`와 `details.order_id`가 함께 옵니다 | 현재 상태를 확인하세요 |
| `409` | `project_not_ordering` | 잠정 주문(`ordering`) 상태가 아니라 확정이나 취소를 할 수 없습니다 | 잠정 주문을 먼저 넣었는지 확인하세요 |
| `409` | `project_locked` | 같은 프로젝트에 주문이 이미 진행 중입니다 | 잠시 후 재시도하세요 |
| `409` | `order_id_mismatch` | 프로젝트에 기록된 주문 id와 다릅니다 | 고객사 DB의 `order_id`를 확인하세요 |
| `409` | `order_id_missing` | 프로젝트에 주문 id가 기록돼 있지 않습니다 | 문의하세요 |
| `409` | `project_not_clonable` | `template_uri`가 없는 프로젝트라 복제할 수 없습니다 | 복제 대상을 확인하세요 |
| `409` | `doc_not_found` | 저장된 문서가 없어 주문할 수 없습니다 | 편집기에서 저장했는지 확인하세요 |
| `413` | `payload_too_large` | 요청 body가 50MB를 넘습니다 | VDP 데이터를 줄이세요 |
| `500` | `internal_error` | 서버 내부 오류입니다 | `request_id`와 함께 문의하세요 |

엔드포인트마다 실제로 낼 수 있는 코드는 [Server API 레퍼런스](/docs/api-reference/introduction)의 각 페이지에 있습니다.

<Note>
  `error.code`는 앞으로 늘어날 수 있습니다. 모르는 코드는 상태 코드 기준으로 처리하세요. 예를 들어 모르는 `409`는 "지금 상태에서는 할 수 없음"으로 다룹니다.
</Note>

## 검증 실패의 `details`

`400 invalid_request`는 `details.issues`에 어긋난 항목을 담아 보냅니다. 각 항목의 `path`가 문제의 헤더나 필드 이름입니다.

```json theme={null}
{
  "error": {
    "code": "invalid_request",
    "message": "요청 형식이 맞지 않습니다",
    "details": {
      "issues": [
        {
          "code": "invalid_format",
          "path": ["edicus-uid"],
          "message": "allowed: a-z A-Z 0-9 _ ! @ - + ="
        }
      ]
    },
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

`issues`의 각 항목은 디버깅용입니다. 화면에 그대로 보여 주거나 `message`를 파싱하지 마세요.

## 200이지만 기대한 값이 없는 경우

아래는 오류가 아니라 정상 응답입니다. 필드가 비었는지 직접 확인하세요.

* 프로젝트 소유자 조회: 없는 프로젝트면 `{"owner": null}`이 옵니다.
* 프리뷰 URL 조회: 프리뷰가 없거나 없는 프로젝트면 `{"urls": []}`가 옵니다.
* 폰트 그룹 조회: 없는 그룹이면 `{"font_group_id": "...", "fonts": null}`이 옵니다.
* 프리뷰 URL 여러 개 조회: 프리뷰 정보가 없는 항목은 `results[i].error`가 `no_preview_urls`입니다.

## 리소스 호스트

`resource-dot-edicusbase.appspot.com`(`/resapi`, `/manapi`)은 오류 모양이 다릅니다. `error` 객체가 없습니다.

| 상황 | 상태 | body |
| - | - | - |
| 파라미터 누락 | `400` | JSON 문자열 하나. 예: `"no option"` |
| 인증 실패 | `401` | `{"code": ...}`. code는 `invalid-edicus-api-key`, `no-edicus-id-token`, `token-expired`, `invalid-role` 중 하나 |
| 그 밖의 모든 실패 | `500` | 던져진 오류를 그대로 직렬화한 것이라 빈 객체 `{}`인 경우가 많습니다 |

body만으로는 원인을 알 수 없는 경우가 많으니 상태 코드와 호출한 엔드포인트를 함께 로그에 남기세요.

## 실패 판정 순서

응답 하나를 받았을 때 아래 순서로 보시면 됩니다.

<Steps>
  <Step title="HTTP 상태 코드가 2xx가 아니다">
    실패입니다. `api.edicus.io`라면 `error.code`로 원인을 구분하고 `request_id`를 로그에 남깁니다. 리소스 호스트라면 위 표를 보세요.
  </Step>

  <Step title="2xx다">
    성공입니다. 비동기 복제는 `202`로 옵니다. 다만 위 "200이지만 기대한 값이 없는 경우"에 해당하는 엔드포인트는 필드가 비었는지 직접 확인하세요.
  </Step>
</Steps>

## 타임아웃

Edicus는 요청 타임아웃을 따로 규정하지 않습니다. 호출하는 쪽에서 거세요. 30초를 기본으로 두시면 대부분 충분합니다.

```ts theme={null}
const res = await fetch(url, { signal: AbortSignal.timeout(30_000) })
```

<Note>
  포토북처럼 이미지가 많은 프로젝트의 동기 복제는 수십 초 이상 걸릴 수 있습니다. 타임아웃을 늘리기보다 비동기 복제(`POST /v2/projects/:project_id/clone_async`)를 쓰시는 편이 낫습니다.
</Note>

## uid 형식 오류

사용자 단위 요청에 `edicus-uid` 헤더가 없거나 [형식 규칙](/docs/concepts/authentication#uid)을 어기면 `400 invalid_request`입니다. 어느 엔드포인트가 이 헤더를 요구하는지는 [Server API 레퍼런스](/docs/api-reference/introduction)에 표시돼 있으니, 보내기 전에 호출하는 쪽에서 먼저 막아 두시면 디버깅이 쉬워집니다.

## 다음 단계

<Columns cols={2}>
  <Card title="Server API 레퍼런스" icon="server" href="/docs/api-reference/introduction">
    엔드포인트별 오류 응답을 봅니다.
  </Card>

  <Card title="주문" icon="cart-shopping" href="/docs/server/orders">
    잠정 주문과 확정 주문의 흐름을 봅니다.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.