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:

JSON
{
  "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

CodeMeaningWhen it happens
200OKSuccessful read, search, update, delete, or restore
201CreatedDocument successfully inserted
202AcceptedAsync job queued (returns job_id and poll_url)
400Bad RequestMalformed JSON, missing required fields, invalid query parameters
401UnauthorizedMissing Authorization header, invalid API key, or expired key
402Plan Limit ExceededPlan quota exhausted (storage, documents, or queries per month)
403ForbiddenYour key doesn't have permission for this operation
404Not FoundUnknown doc_id or job_id
409ConflictAttempting to delete an already-deleted document, or restore an active one
422Unprocessable EntityUnsupported file type, invalid embedding dimension, or malformed content
429Too Many RequestsRate limit exceeded for your plan
500Internal Server ErrorUnexpected server failure — retryable
503Service UnavailableService 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:

JSON
{
  "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.

Use exponential backoff with jitter:

Python
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:

CodeRetryable?Action
429YesBack off exponentially, then retry
500YesRetry with backoff — likely a transient server issue
503YesService temporarily unavailable — wait and retry
400NoFix the request (check parameters, body format)
401NoCheck your API key — it may be missing, invalid, or revoked
402NoQuota exhausted — upgrade plan or reduce usage
403NoYour key lacks permission for this operation
404NoThe resource doesn't exist — verify the ID
409NoCheck the document's current state before retrying the operation
422NoFix 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:

Go
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:

Go
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.