Erros
Todos os erros são devolvidos num formato único - ok: false mais o objeto error:
{ "ok": false, "error": { "code": "INVALID_API_KEY", "message": "Invalid API key" }}code: códigoUPPER_SNAKE_CASElegível por máquina, estável, não muda sem um bump de versão major. Faz o switch por ele, não pormessage.message: explicação legível por humanos, em inglês. O texto pode ser afinado sem bump.details: opcional. ParaINVALID_INPUTé um array{ path, message }com cada campo que falhou. ParaINVALID_FIELD, cada elemento leva tambémexpectedcom o nome canónico do campo.docs: opcional, ligação para a secção desta página quando existe uma explicação mais detalhada para o código.request_id: acrescentado apenas às respostas5xxeBAD_JSON; indica-o quando contactares o suporte.
Códigos HTTP
Seção intitulada “Códigos HTTP”| Estado | Significado |
|---|---|
200 OK | Sucesso |
400 Bad Request | JSON inválido, campos em falta ou validação Zod falhada |
401 Unauthorized | X-Api-Key ausente ou inválido |
402 Payment Required | É preciso um plano / pack superior, ou a subscrição está inativa |
403 Forbidden | A chave é válida, mas não tem permissões (scope, origin, suspended) |
404 Not Found | O recurso, a chave ou o job não existe |
422 Unprocessable Entity | A entrada é válida, mas o motor não conseguiu calcular |
429 Too Many Requests | Rate limit excedido, créditos ou spend-cap esgotados |
500 Internal Server Error | Erro interno - do nosso lado, indica o request_id |
503 Service Unavailable | Gateway de IA / fila temporariamente indisponíveis |
Códigos de erro
Seção intitulada “Códigos de erro”Autenticação (401)
Seção intitulada “Autenticação (401)”| Código | Significado |
|---|---|
MISSING_API_KEY | O cabeçalho X-Api-Key não foi enviado |
INVALID_API_KEY | A chave não existe ou foi revogada |
MISSING_BEARER | O esquema Authorization: Bearer foi escolhido, mas o token está em falta |
WRONG_AUTH_METHOD | Foi enviado X-Api-Key para /v1/me/* (só Bearer). Para verificar a chave - GET /v1/auth/keys/me; para chamadas à API - qualquer /v1/* com X-Api-Key |
Permissões (403)
Seção intitulada “Permissões (403)”| Código | Significado |
|---|---|
INSUFFICIENT_SCOPE | A chave não tem permissões para esta ação |
DOMAIN_MISMATCH / FORBIDDEN_ORIGIN | A origin do pedido não está na allow-list da chave |
ENDPOINT_NOT_IN_SCOPE | A chave está limitada a uma lista de endpoints (allowed_endpoints), e este caminho não está na lista. details indica a lista |
KEY_SUSPENDED | A chave está suspensa |
Faturação (402)
Seção intitulada “Faturação (402)”| Código | Significado |
|---|---|
PLAN_UPGRADE_REQUIRED | O endpoint requer um plano superior ou um pack add-on |
PLAN_PACK_MISMATCH | O endpoint faz parte de um pack que a chave não tem |
SUBSCRIPTION_EXPIRED | O período pago terminou há mais de 3 dias. Nos primeiros 3 dias após o fim, os pedidos continuam a ser servidos segundo o plano e trazem o cabeçalho X-Subscription-Grace-Ends. details contém expired_at, grace_ended_at e renew_url |
Rate limiting e créditos (429)
Seção intitulada “Rate limiting e créditos (429)”| Código | Significado |
|---|---|
RATE_LIMIT | Limite de RPM excedido. A resposta contém o cabeçalho Retry-After: <sec> |
CREDITS_EXHAUSTED | O orçamento mensal de créditos está esgotado |
SPEND_CAP_REACHED | Atingido o limite de gastos em endpoints de IA |
KEY_BUDGET_EXHAUSTED | A chave esgotou o seu próprio orçamento (credits_cap_cycle). A conta pode ainda ter créditos: a restrição aplica-se só a esta chave |
Validação (400)
Seção intitulada “Validação (400)”| Código | Significado |
|---|---|
INVALID_INPUT | Um ou mais campos falharam a validação Zod. details - array { path, message } |
INVALID_FIELD | O corpo contém um nome de campo abreviado em vez do canónico. Ver abaixo |
MISSING_FIELDS | Falta um campo obrigatório |
BAD_JSON | JSON inválido no corpo do pedido |
Exemplo de 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
Seção intitulada “INVALID_FIELD”Os endpoints de mapas aceitam apenas nomes de campos completos. As formas abreviadas são rejeitadas com 400, em vez de serem lidas em silêncio:
| Enviado | Campo canónico | O que se espera |
|---|---|---|
lat | latitude | graus decimais, norte positivo |
lng, lon, long | longitude | graus decimais, leste positivo |
tz com número, tzOffset, utcOffset, timezone_offset | timezoneOffset | número de horas em relação a UTC, p. ex. 5.75 |
tz com nome, timeZone, time_zone | timezone | nome de zona IANA, p. ex. Europe/Kyiv, ou auto |
A resposta enumera todos os campos encontrados de uma só vez, pela ordem do corpo do pedido, por isso a renomeação faz-se numa única passagem:
{ "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 indica o caminho completo, por isso na sinastria vê-se de que mapa se trata: chart1.lat, e não apenas lat.
O mesmo código é devolvido quando não é possível interpretar timezone: valor vazio, uma abreviatura como EST (é -5 fixo o ano inteiro, por isso Nova Iorque em julho fica errada em uma hora), um nome que não existe na base de fusos horários, um desvio escrito como texto, como +03:00, ou auto sem latitude e longitude. A mensagem sugere o que enviar em vez disso, e path aponta para o objeto, por exemplo chart2.timezone.
Duas coisas a ter em conta.
Isto não é uma descontinuação. Os nomes abreviados nunca fizeram parte do contrato nem apareceram em /openapi.json como campos do corpo. Até 2026-08-01 não funcionavam, eram ignorados em silêncio: as coordenadas têm zero por omissão, por isso o mapa era calculado para 0°N 0°E em UTC. O ascendente, as casas e tudo o que deriva deles estava errado, e a resposta não o dizia. Por isso as formas abreviadas não voltam a ser aceites: dois nomes para um campo para sempre, e tz é ainda por cima ambíguo (número ou nome de zona).
Um point aninhado é outra coisa. Em /v1/acg-zones, /v1/acg/by-category, /v1/ccg-analysis e /v1/eclipse-analysis o objeto point: { lat, lng } usa nomes de campo declarados, e passam.
Coordenadas em falta: 400 a partir de 2026-11-09
Seção intitulada “Coordenadas em falta: 400 a partir de 2026-11-09”O nome certo do campo é metade do trabalho. Um corpo sem latitude e longitude também dava 200, porque as coordenadas tinham zero por omissão, e a resposta descrevia 0°N 0°E.
Até 2026-11-09 um pedido destes funciona, mas a resposta traz Deprecation: true, Sunset, Warning e o campo _warning no corpo. A partir de 2026-11-09 é 400 INVALID_INPUT com latitude is required, in decimal degrees.
latitude: 0, longitude: 0 explícitos continuam válidos: a questão é o campo ausente, não o valor. timezoneOffset mantém o zero por omissão, porque ali zero significa UTC. city não substitui as coordenadas: ninguém a geocodifica. A verificação aplica-se apenas a objetos que trazem ao mesmo tempo date e time.
Erros de cálculo (422)
Seção intitulada “Erros de cálculo (422)”| Código | Significado |
|---|---|
CALCULATION_ERROR | A entrada é válida, mas o motor não conseguiu calcular (por exemplo, latitude polar para Placidus). Experimenta Whole Sign / Equal ou outra data. |
Do servidor
Seção intitulada “Do servidor”| Código | HTTP | Significado |
|---|---|---|
INTERNAL_ERROR | 500 | Erro inesperado. Registamo-lo com stack trace, indica o request_id |
GATEWAY_UNAVAILABLE / AI_UNAVAILABLE / LLM_UNAVAILABLE | 503 | Gateway de IA indisponível. Retry com backoff exponencial |
QUEUE_UNAVAILABLE | 503 | A fila de tarefas em segundo plano está indisponível |
Tratamento no código do cliente
Seção intitulada “Tratamento no código do cliente”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})` : ''}`); }}Faz o switch por code, não por message. Os SDK oficiais (@astroway/sdk, astroway) lançam subclasses de erro tipadas por estado HTTP (AuthenticationError, RateLimitError, BadRequestError …), por isso não vais ter de escrever o código de tratamento.
Pedido de suporte
Seção intitulada “Pedido de suporte”Para nos contactares, envia para support@astroway.info:
- O
request_idda resposta (se for um5xx) - O endpoint e o corpo JSON abreviado (sem segredos)
- O comportamento esperado vs o real
- O fuso horário e a hora aproximada do pedido (UTC)
A resposta chega dentro do SLA do teu plano (48 h Starter, 24 h Pro, próprio para Enterprise).