> ## Documentation Index
> Fetch the complete documentation index at: https://docs.etho.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Ad copy generations

> Create, poll, and list workspace-grounded Meta ad-copy jobs

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.