API Reference
Errors
HTTP status codes, error response format, and retry guidance.
Error response format
When a request fails, Aether returns a JSON body with an error message, an optional machine-readable code, and the request id when one is available:
{
"error": "Document not found",
"code": "document_not_found",
"request_id": "req_..."
}
The code and request_id fields may be null for generic validation errors. Include request_id when contacting support.
Status codes
| Code | Meaning | When it happens |
|---|---|---|
200 | OK | Successful read, search, update, delete, or restore |
201 | Created | Document successfully inserted |
202 | Accepted | Async job queued (returns job_id and poll_url) |
400 | Bad Request | Malformed JSON, missing required fields, invalid query parameters |
401 | Unauthorized | Missing Authorization header, invalid API key, or expired key |
402 | Plan Limit Exceeded | Plan quota exhausted (storage, documents, or queries per month) |
403 | Forbidden | Your key doesn't have permission for this operation |
404 | Not Found | Unknown doc_id or job_id |
409 | Conflict | Attempting to delete an already-deleted document, or restore an active one |
422 | Unprocessable Entity | Unsupported file type, invalid embedding dimension, or malformed content |
429 | Too Many Requests | Rate limit exceeded for your plan |
500 | Internal Server Error | Unexpected server failure — retryable |
503 | Service Unavailable | Service temporarily unavailable — retryable |
Scoping errors
If an API key is configured as multi-tenant, every read and write must name a partition (see Multi-tenant patterns). An unscoped call returns 400 with a distinct code:
{
"error": "This API key is multi-tenant, so every search must name a partition. Scope this call to a partition…",
"code": "partition_required"
}
Fix it by going through a partition handle — client.partition("<end-client-id>").search(...) — instead of the bare client. The SDKs surface this as a dedicated typed error so you can catch it precisely: PartitionRequiredError (Python, TypeScript), ErrPartitionRequired (Go, via errors.Is), PartitionRequiredException (.NET). It is never retryable — it's a programming error, not a transient failure.
Rate limiting
Aether enforces per-organization rate limits on all authenticated endpoints. Limits vary by plan tier and are shown in the Dashboard.
When you exceed the limit, the API returns 429 Too Many Requests with a retry-after header in seconds. Back off and retry after that delay.
Recommended backoff strategy
Use exponential backoff with jitter:
import time
import random
def retry_with_backoff(fn, max_retries=5):
for attempt in range(max_retries):
try:
return fn()
except Exception as e:
if not is_retryable(e):
raise
delay = min(2 ** attempt + random.uniform(0, 1), 30)
time.sleep(delay)
raise Exception("Max retries exceeded")
Retry guidance
Not all errors should be retried. Use this table to decide:
| Code | Retryable? | Action |
|---|---|---|
429 | Yes | Back off exponentially, then retry |
500 | Yes | Retry with backoff — likely a transient server issue |
503 | Yes | Service temporarily unavailable — wait and retry |
400 | No | Fix the request (check parameters, body format) |
401 | No | Check your API key — it may be missing, invalid, or revoked |
402 | No | Quota exhausted — upgrade plan or reduce usage |
403 | No | Your key lacks permission for this operation |
404 | No | The resource doesn't exist — verify the ID |
409 | No | Check the document's current state before retrying the operation |
422 | No | Fix the input — check file type, embedding dimensions, or content format |
Go error handling
The Go SDK returns error as the second value from every method. For basic usage, check err != nil. For finer control — like distinguishing a rate limit from a bad request — inspect the underlying HTTP status:
results, err := client.Search(ctx, "query", 5)
if err != nil {
// The SDK wraps HTTP errors with the status code in the message.
// For production retry logic, check the error string:
if strings.Contains(err.Error(), "429") {
// Rate limited — back off and retry
time.Sleep(time.Second)
} else if strings.Contains(err.Error(), "401") {
log.Fatal("Invalid API key — check AETHER_API_KEY")
} else {
log.Printf("Search failed: %v", err)
}
}
Context cancellation. Always pass the request context in HTTP handlers. If a client disconnects, the context is cancelled and the Aether call aborts cleanly:
func handleSearch(w http.ResponseWriter, r *http.Request) {
// r.Context() carries the request's deadline and cancellation signal
results, err := client.Search(r.Context(), query, 5)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ...
}
Use context.Background() only in main(), CLI tools, or background goroutines where there's no incoming request.