본문으로 건너뛰기
Sursa Developers

오류

Sursa는 요청의 성공 여부를 HTTP 상태 코드로 알리고, 실패한 이유를 바뀌지 않는 오류 코드로 알려요. 모든 오류 응답은 형태가 같아요.

{
  "detail": "the cursor is malformed or was issued for different filters",
  "error": {
    "code": "api.cursor_invalid",
    "message": "the cursor is malformed or was issued for different filters",
    "details": {"parameter": "cursor"},
    "request_id": "01JB7Q2M4X9V3K8T5N6P0R1S2A",
    "doc_url": "https://developers.sursa.com/errors/api.cursor_invalid"
  }
}
필드의미
error.code<area>.<reason> 형식의 고정된 코드. 처리 분기는 이 값을 기준으로 하세요.
error.message사람이 읽을 수 있는 영어 설명. 문구가 언제든 바뀔 수 있으니 파싱하지 마세요.
error.details파라미터, 한도, 재시도 시점처럼 오류에 관한 구조화된 정보. 필드는 코드에 따라 달라요.
error.request_id이 요청의 ID. X-Request-Id 응답 헤더 값과 같아요.
error.doc_url이 포털에 있는 해당 코드의 페이지.
detail기존 연동을 위해 남겨 둔 이전 형식의 오류. 새 코드에서는 error 값을 읽으세요.

모든 코드에는 오류 코드 아래에 HTTP 상태, details 필드, 그 코드를 반환하는 엔드포인트를 담은 페이지가 있어요. API 레퍼런스의 각 엔드포인트 페이지에는 그 엔드포인트가 반환할 수 있는 코드가 나와 있어요.

HTTP 상태

상태의미
200, 201, 202, 204요청이 성공했어요. 202는 요청을 받아들였고 아직 처리 중이라는 뜻이에요.
400요청 형식이 잘못되었어요. 파라미터, 커서, metadata, Idempotency-Key 중 잘못된 값이 있어요. 요청을 고치세요.
401유효한 키가 없어요. 인증을 참고하세요.
402카드 수나 저장 용량처럼 조직 요금제의 한도에 도달했어요.
403키는 유효하지만 이 요청을 수행할 수 없어요. 역할, 스코프, 요금제 기능 중 하나가 없어요.
404객체가 없거나, 키가 그 객체를 볼 수 없어요.
409요청이 현재 상태와 충돌해요. 중복된 external_id, 열려 있는 동기화, 같은 Idempotency-Key로 아직 실행 중인 요청 등이 원인이에요.
410객체가 만료되었거나(예: 24시간이 지난 업로드) 경로가 지원 종료되었어요.
413본문이나 파일이 너무 커요.
415지원하지 않는 파일 형식이거나, 파일 내용이 이름과 맞지 않아요.
422요청 형식은 올바르지만 내용이 유효하지 않아요. 필드가 빠졌거나 값이 범위를 벗어났어요.
429요청이 너무 많거나 할당량을 다 썼어요. 호출 한도와 할당량을 참고하세요.
500, 502, 503, 504Sursa 쪽의 문제예요. 나중에 다시 시도하세요.

유효성 검사 오류

validation.failed 코드의 422 응답에서는 detail 값이 유효성 검사를 통과하지 못한 모든 필드를 위치와 함께 나열해요.

{
  "detail": [
    {"loc": ["body", "title"], "msg": "String should have at least 1 character", "type": "string_too_short"}
  ],
  "error": {"code": "validation.failed", "message": "request validation failed", "request_id": "01JB7…"}
}

재시도할 오류

오류재시도 여부
네트워크 오류나 타임아웃으로 응답이 없음예. 같은 Idempotency-Key로 재시도하세요.
429예. Retry-After에 적힌 초만큼 기다린 뒤 재시도하세요.
500, 502, 503, 504예. 지수 백오프로 재시도하세요. 1초, 2초, 4초… 최대 1분까지 늘리고, 무작위 지터를 더하세요.
409 api.idempotency_in_progress예. Retry-After 이후에 재시도하세요. 이 키로 보낸 첫 요청이 아직 실행 중이에요.
그 밖의 모든 4xx아니요. 같은 요청은 다시 실패해요. 먼저 요청을 고치세요.

POST와 PATCH 요청은 Idempotency-Key를 붙였을 때만 재시도하세요. 그래야 응답을 받지 못해 재시도하더라도 객체가 두 번 생기지 않아요. GET, PUT, DELETE는 그대로 재시도해도 안전해요.

// Retries a request on network errors, 429 and 5xx. The same Idempotency-Key is sent on every attempt.
async function withRetries(send, attempts = 5) {
  for (let attempt = 1; ; attempt++) {
    let response;
    try {
      response = await send();
    } catch (error) {
      if (attempt >= attempts) throw error;
    }
    if (response && response.status !== 429 && response.status < 500) return response;
    if (attempt >= attempts) return response;
    const retryAfter = Number(response?.headers.get("Retry-After"));
    const backoff = Math.min(60, 2 ** (attempt - 1)) * (0.8 + Math.random() * 0.4);
    await new Promise((resolve) => setTimeout(resolve, 1000 * (retryAfter || backoff)));
  }
}

const key = crypto.randomUUID();
const response = await withRetries(() =>
  fetch("https://api.sursa.com/v1/cards/text", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SURSA_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": key,
    },
    body: JSON.stringify({ title: "Refund policy", body_markdown: "Refunds take 14 days." }),
  }),
);

문제 알리기

성공 여부와 관계없이 모든 응답에는 X-Request-Id 헤더가 있고, 모든 오류의 error.request_id에 같은 값이 들어 있어요. 이 값을 직접 남기는 요청 기록과 함께 로그에 저장하세요. 특정 요청에 대해 지원팀에 문의할 때 요청 ID를 보내 주시면 그 요청의 기록을 바로 찾을 수 있어요.

모르는 코드 처리하기

새 오류 코드는 API의 어느 버전에서든 추가될 수 있어요. 모르는 코드는 HTTP 상태로 판단하세요. 모르는 4xx는 그대로는 성공하지 않을 요청으로, 모르는 5xx는 일시적인 문제로 처리하세요.