오류
모든 오류는 단일 형식으로 반환됩니다. ok: false와 error 객체입니다:
{ "ok": false, "error": { "code": "INVALID_API_KEY", "message": "Invalid API key" }}code: 기계가 읽을 수 있는UPPER_SNAKE_CASE코드로, 안정적이며 메이저 버전 변경 없이는 바뀌지 않습니다.message가 아니라 이 값으로 분기하세요.message: 사람이 읽는 영어 설명입니다. 문구는 버전 변경 없이 다듬어질 수 있습니다.details: 선택 사항.INVALID_INPUT의 경우 실패한 필드마다{ path, message }가 담긴 배열입니다.INVALID_FIELD의 경우 각 요소에 표준 필드 이름을 담은expected가 추가됩니다.docs: 선택 사항. 코드에 대한 자세한 설명이 있을 때 이 페이지의 해당 섹션 링크입니다.request_id:5xx와BAD_JSON응답에만 추가됩니다. 지원팀에 문의할 때 이 값을 알려 주세요.
HTTP 코드
섹션 제목: “HTTP 코드”| 상태 | 의미 |
|---|---|
200 OK | 성공 |
400 Bad Request | 잘못된 JSON, 누락된 필드 또는 Zod 검증 실패 |
401 Unauthorized | X-Api-Key가 없거나 유효하지 않음 |
402 Payment Required | 더 높은 요금제 / 팩이 필요하거나 구독이 비활성 상태 |
403 Forbidden | 키는 유효하지만 권한이 없음(scope, origin, suspended) |
404 Not Found | 리소스, 키 또는 job이 존재하지 않음 |
422 Unprocessable Entity | 입력은 유효하지만 엔진이 계산하지 못함 |
429 Too Many Requests | rate limit 초과, 크레딧 또는 spend-cap 소진 |
500 Internal Server Error | 내부 오류로 저희 쪽 문제입니다. request_id를 알려 주세요 |
503 Service Unavailable | AI 게이트웨이 / 큐를 일시적으로 사용할 수 없음 |
Error codes
섹션 제목: “Error codes”인증 (401)
섹션 제목: “인증 (401)”| 코드 | 의미 |
|---|---|
MISSING_API_KEY | X-Api-Key 헤더가 전송되지 않음 |
INVALID_API_KEY | 키가 존재하지 않거나 revoked 상태 |
MISSING_BEARER | Authorization: Bearer 스킴을 선택했지만 토큰이 없음 |
WRONG_AUTH_METHOD | /v1/me/*(Bearer 전용)에 X-Api-Key를 보냄. 키 확인에는 GET /v1/auth/keys/me, API 호출에는 X-Api-Key를 붙인 아무 /v1/*를 사용하세요 |
권한 (403)
섹션 제목: “권한 (403)”| 코드 | 의미 |
|---|---|
INSUFFICIENT_SCOPE | 키에 이 작업에 대한 권한이 없음 |
DOMAIN_MISMATCH / FORBIDDEN_ORIGIN | 요청의 Origin이 키의 allow-list에 없음 |
ENDPOINT_NOT_IN_SCOPE | 키가 엔드포인트 목록(allowed_endpoints)으로 제한되어 있고 이 경로는 목록에 없음. details에 목록이 담김 |
KEY_SUSPENDED | 키가 일시 정지됨 |
과금 (402)
섹션 제목: “과금 (402)”| 코드 | 의미 |
|---|---|
PLAN_UPGRADE_REQUIRED | 엔드포인트에 더 높은 요금제나 add-on 팩이 필요함 |
PLAN_PACK_MISMATCH | 엔드포인트가 키에 없는 팩에 속함 |
SUBSCRIPTION_EXPIRED | 결제 기간이 끝난 지 3일이 넘음. 만료 후 처음 3일 동안은 요청이 여전히 요금제대로 처리되며 X-Subscription-Grace-Ends 헤더가 붙습니다. details에 expired_at, grace_ended_at, renew_url이 담김 |
Rate limiting 및 크레딧 (429)
섹션 제목: “Rate limiting 및 크레딧 (429)”| 코드 | 의미 |
|---|---|
RATE_LIMIT | RPM 한도 초과. 응답에 Retry-After: <sec> 헤더가 포함됨 |
CREDITS_EXHAUSTED | 월간 크레딧 예산 소진 |
SPEND_CAP_REACHED | AI 엔드포인트 지출 한도 도달 |
KEY_BUDGET_EXHAUSTED | 키가 자체 예산(credits_cap_cycle)을 소진함. 계정에는 크레딧이 남아 있을 수 있으며, 제한은 이 키에만 해당됨 |
검증 (400)
섹션 제목: “검증 (400)”| 코드 | 의미 |
|---|---|
INVALID_INPUT | 하나 이상의 필드가 Zod 검증에 실패함. details는 { path, message } 배열 |
INVALID_FIELD | 본문에 표준 이름 대신 짧은 필드 이름이 들어 있음. 아래 참조 |
MISSING_FIELDS | 필수 필드 누락 |
BAD_JSON | 요청 본문의 JSON이 잘못됨 |
400 예시:
{ "ok": false, "error": { "code": "INVALID_INPUT", "message": "Validation failed: date: Invalid input: expected string, received undefined", "details": [ { "path": "date", "message": "Invalid input: expected string, received undefined" }, { "path": "time", "message": "Invalid input: expected string, received undefined" } ] }}INVALID_FIELD
섹션 제목: “INVALID_FIELD”Chart 엔드포인트는 전체 필드 이름만 받습니다. 짧은 형태는 조용히 읽히지 않고 400으로 거부됩니다:
| 보낸 값 | 표준 필드 | 기대하는 값 |
|---|---|---|
lat | latitude | 십진수 도, 북쪽이 양수 |
lng, lon, long | longitude | 십진수 도, 동쪽이 양수 |
숫자가 든 tz, tzOffset, utcOffset, timezone_offset | timezoneOffset | UTC 기준 시간 수, 예: 5.75 |
이름이 든 tz, timeZone, time_zone | timezone | IANA 시간대 이름, 예: Europe/Kyiv, 또는 auto |
응답은 발견된 필드를 요청 본문 순서대로 모두 한 번에 나열하므로 이름 변경을 한 번에 끝낼 수 있습니다:
{ "ok": false, "error": { "code": "INVALID_FIELD", "message": "Unsupported fields: \"tz\", \"lat\", \"lng\". Rename them to \"timezoneOffset\", \"latitude\", \"longitude\". See details for what each one expects.", "details": [ { "path": "tz", "expected": "timezoneOffset", "message": "numeric hours from UTC, e.g. 5.75; a zone name goes in timezone" }, { "path": "lat", "expected": "latitude", "message": "decimal degrees, north positive" }, { "path": "lng", "expected": "longitude", "message": "decimal degrees, east positive" } ], "docs": "https://api.astroway.info/en/errors/#invalid_field" }}path는 전체 경로를 나타내므로 synastry에서는 어느 차트인지 보입니다. 그냥 lat이 아니라 chart1.lat입니다.
timezone을 해석할 수 없을 때도 같은 코드가 반환됩니다: 빈 값, EST 같은 약어(연중 고정 -5이므로 7월의 뉴욕은 한 시간 틀리게 나옵니다), 시간대 데이터베이스에 없는 이름, +03:00처럼 텍스트로 쓴 오프셋, 또는 latitude와 longitude 없는 auto. 메시지는 대신 무엇을 보내야 하는지 알려 주며, path는 chart2.timezone처럼 해당 객체를 가리킵니다.
주의할 점이 두 가지 있습니다.
이것은 deprecation이 아닙니다. 짧은 이름은 한 번도 계약에 포함된 적이 없고 /openapi.json에 본문 필드로 나온 적도 없습니다. 2026-08-01 전까지 이 이름들은 작동하지 않고 조용히 무시되었습니다. 좌표의 기본값이 0이므로 차트는 UTC 기준 0°N 0°E로 계산되었습니다. 어센던트, 하우스, 그리고 이들에서 파생된 모든 것이 틀렸지만 응답은 그 사실을 알려 주지 않았습니다. 그래서 짧은 형태는 다시 허용되지 않습니다. 한 필드에 두 이름을 영구히 둘 수는 없고, 게다가 tz는 모호합니다(숫자인지 시간대 이름인지).
중첩된 point는 다른 경우입니다. /v1/acg-zones, /v1/acg/by-category, /v1/ccg-analysis, /v1/eclipse-analysis에서 point: { lat, lng } 객체는 선언된 필드 이름이며 통과합니다.
좌표 누락: 2026-11-09부터 400
섹션 제목: “좌표 누락: 2026-11-09부터 400”올바른 필드 이름은 절반에 불과합니다. latitude와 longitude가 없는 본문도 200을 반환했습니다. 좌표의 기본값이 0이었기 때문에 응답은 0°N 0°E를 설명했습니다.
2026-11-09 전까지 이런 요청은 작동하지만 응답에 Deprecation: true, Sunset, Warning, 그리고 본문의 _warning 필드가 포함됩니다. 2026-11-09부터는 latitude is required, in decimal degrees와 함께 400 INVALID_INPUT이 반환됩니다.
명시적인 latitude: 0, longitude: 0은 계속 유효합니다. 문제는 값이 아니라 필드가 없다는 것입니다. timezoneOffset은 기본값 0을 유지합니다. 거기서 0은 UTC를 뜻하기 때문입니다. city는 좌표를 대신하지 않습니다. 아무도 이를 지오코딩하지 않습니다. 이 검사는 date와 time을 동시에 가진 객체에만 적용됩니다.
계산 오류 (422)
섹션 제목: “계산 오류 (422)”| 코드 | 의미 |
|---|---|
CALCULATION_ERROR | 입력은 유효하지만 엔진이 계산하지 못함(예: Placidus에서 극지방 위도). Whole Sign / Equal 또는 다른 날짜를 시도해 보세요. |
서버 오류
섹션 제목: “서버 오류”| 코드 | HTTP | 의미 |
|---|---|---|
INTERNAL_ERROR | 500 | 예상치 못한 오류. stack trace와 함께 기록합니다. request_id를 알려 주세요 |
GATEWAY_UNAVAILABLE / AI_UNAVAILABLE / LLM_UNAVAILABLE | 503 | AI 게이트웨이 사용 불가. 지수 backoff로 재시도하세요 |
QUEUE_UNAVAILABLE | 503 | 백그라운드 작업 큐 사용 불가 |
클라이언트 코드에서의 처리
섹션 제목: “클라이언트 코드에서의 처리”type ApiError = { ok: false; error: { code: string; message: string; details?: unknown; request_id?: string; };};
async function chart(input: ChartInput) { const res = await fetch('https://api.astroway.info/v1/chart', { method: 'POST', headers: { 'X-Api-Key': process.env.ASTROWAY_API_KEY!, 'Content-Type': 'application/json', }, body: JSON.stringify(input), });
const body = await res.json(); if (!body.ok) { throw new AstrowayError(body.error.code, body.error.message, body.error.request_id); }
return body.data;}
class AstrowayError extends Error { constructor( public code: string, message: string, public requestId?: string, ) { super(`[${code}] ${message}${requestId ? ` (request_id=${requestId})` : ''}`); }}message가 아니라 code로 분기하세요. 공식 SDK(@astroway/sdk, astroway)는 HTTP 상태에 따라 타입이 지정된 오류 하위 클래스(AuthenticationError, RateLimitError, BadRequestError …)를 던지므로 처리 코드를 직접 작성할 필요가 없습니다.
지원 요청
섹션 제목: “지원 요청”문의할 때는 support@astroway.info로 다음을 보내 주세요:
- 응답의
request_id(5xx인 경우) - 엔드포인트와 축약한 JSON body(비밀 정보 제외)
- 기대한 동작과 실제 동작
- 시간대와 대략적인 요청 시각(UTC)
답변은 플랜의 SLA 이내에 드립니다(Starter 48시간, Pro 24시간, Enterprise는 별도 협의).