Skip to content
AstroWay/api v2.201.7 · docs
all systems operational

Errors

Every error comes back in a single shape - ok: false plus an error object:

{
"ok": false,
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid API key"
}
}
  • code: machine-readable UPPER_SNAKE_CASE code, stable, never changes without a major bump. Switch on this, not on message.
  • message: human-readable explanation in English. Wording may be refined without a bump.
  • details: optional. For INVALID_INPUT it’s an array of { path, message } for each failed field. For INVALID_FIELD each entry also carries expected, the canonical field name.
  • docs: optional, a link to the section of this page that explains the code in more depth.
  • request_id: added only to 5xx and BAD_JSON responses; quote it when contacting support.
StatusMeaning
200 OKSuccess
400 Bad RequestMalformed JSON, missing fields, or failed Zod validation
401 UnauthorizedMissing or invalid X-Api-Key
402 Payment RequiredA higher plan / add-on pack is required, or the subscription is inactive
403 ForbiddenKey is valid but lacks permission (scope, origin, suspended)
404 Not FoundResource, key, or job does not exist
422 Unprocessable EntityInput is valid but the engine could not compute it
429 Too Many RequestsRate limit exceeded, credits exhausted, or spend cap hit
500 Internal Server ErrorOur fault - quote request_id
503 Service UnavailableAI gateway / queue temporarily down
CodeMeaning
MISSING_API_KEYThe X-Api-Key header was not sent
INVALID_API_KEYKey does not exist or was revoked
MISSING_BEARERAuthorization: Bearer scheme selected but token missing
CodeMeaning
INSUFFICIENT_SCOPEKey lacks permission for this action
DOMAIN_MISMATCH / FORBIDDEN_ORIGINRequest origin is not in the key’s allow-list
ENDPOINT_NOT_IN_SCOPEThe key is limited to a list of endpoints (allowed_endpoints) and this path is not on it. details names the list
KEY_SUSPENDEDKey has been suspended
CodeMeaning
PLAN_UPGRADE_REQUIREDEndpoint requires a higher plan or an add-on pack
PLAN_PACK_MISMATCHEndpoint is in a pack the key doesn’t have
SUBSCRIPTION_EXPIREDThe paid period ended more than 3 days ago. For the first 3 days after it ends, requests are still served on your plan and carry an X-Subscription-Grace-Ends header. details has expired_at, grace_ended_at and renew_url
CodeMeaning
RATE_LIMITRPM limit exceeded. Response carries a Retry-After: <sec> header
CREDITS_EXHAUSTEDMonthly credit budget used up
SPEND_CAP_REACHEDAI-endpoint spend cap reached
KEY_BUDGET_EXHAUSTEDThe key reached its own budget (credits_cap_cycle). The account may still have credits: this ceiling belongs to the one key
CodeMeaning
INVALID_INPUTOne or more fields failed Zod validation. details is an array of { path, message }
INVALID_FIELDThe body uses a short field name instead of the canonical one. See below
MISSING_FIELDSA required field is missing
BAD_JSONMalformed JSON in the request body

Example 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" }
]
}
}

Chart endpoints accept full field names only. Short forms are refused with a 400 instead of being read silently:

SentCanonical fieldWhat it expects
latlatitudedecimal degrees, north positive
lng, lon, longlongitudedecimal degrees, east positive
tz with a number, tzOffset, utcOffset, timezone_offsettimezoneOffsetnumeric hours from UTC, e.g. 5.75
tz with a name, timeZone, time_zonetimezonean IANA zone name such as Europe/Kyiv, or auto

The response lists every field it found at once, in request-body order, so the rename is a single pass:

{
"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 is the full path, so a synastry body shows which chart carries it: chart1.lat, not just lat.

The same code comes back when timezone cannot be resolved: an empty value, an abbreviation such as EST (a fixed -5 all year, so New York in July would come out an hour wrong), a name the tz database does not know, an offset written as text such as +03:00, or auto without latitude and longitude. The message says what to send instead, and path points at the object, for example chart2.timezone.

Two things worth knowing.

This is not a deprecation. The short names were never part of the contract and never appeared in /openapi.json as body fields. Before 2026-08-01 they did not work, they were silently ignored: the coordinates carry a zero default, so the chart was computed for 0°N 0°E at UTC. The ascendant, the houses and everything derived from them were wrong, and the response said nothing about it. That is why the short forms are not accepted back: it would mean two names for one field forever, and tz is ambiguous on top of that (a number or a zone name).

A nested point is a different thing. On /v1/acg-zones, /v1/acg/by-category, /v1/ccg-analysis and /v1/eclipse-analysis the object point: { lat, lng } uses the declared field names and passes.

Missing coordinates: a 400 from 2026-11-09

Section titled “Missing coordinates: a 400 from 2026-11-09”

Getting the field name right is half of it. A body without latitude and longitude also answered 200, because the coordinates carried a zero default and the response described 0°N 0°E.

Until 2026-11-09 such a request still works, but the response carries Deprecation: true, Sunset, Warning and a _warning field in the body. From 2026-11-09 it is a 400 INVALID_INPUT with latitude is required, in decimal degrees.

An explicit latitude: 0, longitude: 0 stays valid: the problem is the missing field, not the value. timezoneOffset keeps its zero default, because 0 there means UTC. city does not stand in for coordinates: nothing geocodes it. The check applies only to objects carrying both date and time.

CodeMeaning
CALCULATION_ERRORInput is valid but the engine could not compute (e.g. a polar latitude for Placidus). Try Whole Sign / Equal or a different date.
CodeHTTPMeaning
INTERNAL_ERROR500Unexpected error. Logged with a stack trace - quote request_id
GATEWAY_UNAVAILABLE / AI_UNAVAILABLE / LLM_UNAVAILABLE503AI gateway down. Retry with exponential backoff
QUEUE_UNAVAILABLE503Background-job queue down
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})` : ''}`);
}
}

Switch on code, not on message. The official SDKs (@astroway/sdk, astroway) throw typed error subclasses by HTTP status (AuthenticationError, RateLimitError, BadRequestError, …), so you won’t need to write this yourself.

To open a ticket, email support@astroway.info:

  1. The request_id from the response (for 5xx)
  2. The endpoint and a trimmed JSON body (no secrets)
  3. Expected vs actual behavior
  4. Timezone and approximate request time (UTC)

Replies land within your plan’s SLA (48 h Starter, 24 h Pro, custom for Enterprise).

Was this helpful?
Suggest an edit

Last updated: