Skip to main content
Base URL: the Olympus app origin (production https://app.etho.net). All responses set Cache-Control: no-store.

Create a job

POST /api/v1/ad-copy-generations returns 202 with a job and a Location of /api/v1/ad-copy-generations/{id}. Required JSON:
  • platform: facebook or instagram
  • awareness_level: unaware, problem_aware, or solution_aware
  • brief: trimmed, 1 to 280 characters
Optional:
  • offer_id: an active offer from GET /api/v1/offers
  • copy_length: short, medium, long, extra_long, or blog (default medium). blog is Facebook only.
Unknown fields are rejected. You cannot send a variation count, avatar, Think report, voice, or freeform prompt. Grounding always comes from the workspace Brand Codex plus the optional offer. The body must be uncompressed application/json, at most 32 KiB, with exactly one Idempotency-Key (^[A-Za-z0-9._:-]{1,128}$). Use a UUID per intended job. Keys never expire. Same key and same canonical body replay the current job (202). Same key and a different body return idempotency_mismatch.

Poll a job

GET /api/v1/ad-copy-generations/{id} returns 200 for queued, running, succeeded, and failed. A failed generation is still 200 with status=failed and a safe error. Poll until terminal. There are no completion webhooks in this preview. The job never echoes brief or grounding. A successful result has headline, primary_text, optional description, artifact_id, app_url, and optional sources: the ids of the workspace knowledge files the brand brief drew on ([{ "file_id": "…" }]). The brief text itself is never returned and is removed from the job once it finishes. Later edits in the Olympus UI do not change that snapshot.

List jobs

GET /api/v1/ad-copy-generations returns newest first. Default page size 20, maximum 100. Pass cursor from next_cursor. Optional status filter.

List offers

GET /api/v1/offers returns active offers as {id, object:"offer", name} so you can fill offer_id. Archived and other-workspace offers are omitted.

Idempotency

The server hashes the key and the canonical request (sorted keys, defaults applied). The first accepted unique key consumes one monthly allocation unit even if generation later fails. Authentication, validation, entitlement, quota rejection, and replay consume none.

Errors

Failures use application/problem+json with type, title, status, detail, and request_id. type URIs are https://docs.getolympus.com/problems/{code}.

invalid_request

https://docs.getolympus.com/problems/invalid_request (400). Malformed JSON, unknown fields, missing Idempotency-Key, oversize body, Content-Encoding, wrong Content-Type, or a bad cursor.

invalid_auth

https://docs.getolympus.com/problems/invalid_auth (401) plus WWW-Authenticate: Bearer. Missing, malformed, revoked, unbound, ola_, or olr_ credentials.

forbidden

https://docs.getolympus.com/problems/forbidden (403). The token’s role cannot perform the action (Viewer create), or membership was removed.

preview_disabled

https://docs.getolympus.com/problems/preview_disabled (403). The workspace has no enabled public API access row, or allocation is unset.

workspace_frozen

https://docs.getolympus.com/problems/workspace_frozen (403). Billing freeze. Reads and writes are blocked.

not_found

https://docs.getolympus.com/problems/not_found (404). Unknown job id, or an offer that is archived or belongs to another workspace. The response does not disclose which.

missing_brand

https://docs.getolympus.com/problems/missing_brand (422). The workspace has no Brand Codex.

grounding_too_large

https://docs.getolympus.com/problems/grounding_too_large (422). Trusted Brand/offer/avatar/Think facts exceed 16 KiB per field or 128 KiB total. Nothing is truncated.

idempotency_mismatch

https://docs.getolympus.com/problems/idempotency_mismatch (422). The key was already used with a different body.

quota_exceeded

https://docs.getolympus.com/problems/quota_exceeded (429) plus Retry-After and reset_at (start of next UTC month).

rate_limited

https://docs.getolympus.com/problems/rate_limited (429) plus Retry-After. Per-instance limiter on the token or on malformed-auth IP.

unavailable

https://docs.getolympus.com/problems/unavailable (503). Global PUBLIC_API_ENABLED is off for mutations, or an internal dependency is down. GET still works when the flag is off.