Errors
서비스 장애와 게이트웨이 에러를 하나의 에러 모델로 정규화합니다.
실패한 호출은 어디서 실패했든 같은 JSON 본문으로 돌아옵니다. Catch Table이 거부한 취소와 게이트웨이가 발생시킨 rate limit이 하나의 구조를 공유하므로, 에러 처리 코드는 한 번만 작성하면 카탈로그의 모든 서비스에 재사용할 수 있습니다.
2xx가 아닌 모든 응답의 본문은 아래 형태입니다. provider와 retryable은 선택 필드이고, 나머지 세 필드는 항상 존재합니다.
{
"error": {
"code": "provider_unavailable",
"message": "Catch Table did not answer within the gateway deadline.",
"request_id": "req_01J8QGX2M4",
"provider": "catch-table",
"retryable": true
}
}code: 기계가 분기할 수 있는 안정적인 식별자입니다. 아래 표의 8개 값 중 하나입니다.message: 사람이 읽는 설명입니다. 파싱하지 않습니다.request_id: 이 요청의 식별자입니다. 지원 문의 시 함께 전달합니다.provider: 에러가 제휴 서비스에서 발생했을 때만 존재합니다.retryable: 같은 요청을 backoff와 함께 재시도하면 성공할 수 있을 때 true입니다.HTTP status는 에러를 큰 범주로 묶고, code는 정확한 케이스를 지정합니다. 매핑은 고정되어 있습니다.
| HTTP | code | 의미 | 재시도 |
|---|---|---|---|
| 400 | invalid_request | 요청이 스키마 검증을 통과하지 못했습니다. | 불가 |
| 401 | unauthorized | managed key가 없거나 유효하지 않습니다. | 불가 |
| 403 | scope_denied | key의 scope가 이 서비스 또는 API를 허용하지 않습니다. | 불가 |
| 404 | not_found | 경로의 리소스가 존재하지 않습니다. | 불가 |
| 409 | conflict | 리소스의 현재 상태와 충돌합니다. 예: 이미 소진된 슬롯. | 불가 |
| 422 | provider_rejected | 서비스의 정책이 요청을 거부했습니다. | 불가 |
| 429 | rate_limited | rate limit을 초과했습니다. Retry-After 헤더가 함께 옵니다. | 가능 |
| 502 | provider_unavailable | 제휴 서비스가 응답하지 않았습니다. | 가능 |
provider 필드가 에러의 출처를 알려줍니다. 필드가 있으면 요청이 게이트웨이를 통과해 서비스까지 도달했고 거기서 거부되거나 실패했다는 뜻입니다. 401, 403, 429처럼 게이트웨이에서 끝난 에러에는 이 필드가 없습니다. 반대로 422 provider_rejected와 502 provider_unavailable은 항상 서비스에서 발생하므로 필드가 항상 존재합니다.
이 구분이 대응 방법을 결정합니다. 게이트웨이 에러는 요청 자체(key, scope, 스키마)를 고치면 해결됩니다. 서비스 에러는 서비스 쪽 상태(취소 정책, 슬롯 재고, 가용성)가 원인이므로, 요청을 고쳐도 결과가 달라지지 않는 경우가 많습니다.
모든 에러에는 request_id가 포함됩니다. 게이트웨이 로그와 서비스 호출 기록이 이 식별자 하나로 연결되어 있으므로, 지원 문의 시 이 값을 함께 전달하면 재현 과정 없이 바로 추적할 수 있습니다. 예상하지 못한 에러는 code와 request_id를 함께 로깅해 두는 것을 권장합니다.
매장의 취소 마감이 지난 예약에 을 호출하면 422가 반환됩니다. sandbox key가 없다면 에서 발급합니다.
curl -X DELETE "https://api.frism.dev/v1/catch-table/reservations/rsv_01J8QDGT2K" \
-H "Authorization: Bearer frism_test_k3xample"{
"error": {
"code": "provider_rejected",
"message": "The venue's cancellation window has closed.",
"request_id": "req_01J8QH54N8",
"provider": "catch-table",
"retryable": false
}
}에러 처리 로직은 항상 code로 분기합니다. message는 사람에게 보여주거나 로그에 남기는 용도이며, 표현이 예고 없이 바뀔 수 있습니다. status만으로 분기하는 것도 피합니다. status는 범주만 알려주고, 같은 status 아래에 더 구체적인 code가 추가될 수 있기 때문입니다.
const response = await fetch(
"https://api.frism.dev/v1/catch-table/reservations/rsv_01J8QDGT2K",
{
method: "DELETE",
headers: { Authorization: "Bearer frism_test_k3xample" },
},
);
if (!response.ok) {
const { error } = (await response.json()) as {
error: {
code: string;
message: string;
request_id: string;
provider?: string;
retryable?: boolean;
};
};
switch (error.code) {
case "provider_rejected":
// 정책상 거부입니다. 재시도 대신 사용자에게 안내합니다.
break;
case "rate_limited":
case "provider_unavailable":
// backoff 후 재시도합니다.
break;
default:
// 예상하지 못한 code는 request_id와 함께 로깅합니다.
console.error(error.code, error.request_id);
}
}재시도해도 되는 에러와 backoff 규칙은 에서 다룹니다. API별 에러 응답 전체는 각 서비스의 API 레퍼런스(예: )에 문서화되어 있습니다.