<!-- Generated from openapi/v2.json (md/ideas.md) — do not edit. Run: npm run generate -->

[Interactive reference](https://api-docs.syllaby.io/) · [OpenAPI 3.1 spec](https://api-docs.syllaby.io/openapi/v2.json) · [Docs index (llms.txt)](https://api-docs.syllaby.io/llms.txt)

# Ideas

Discover and rank content ideas for a keyword on a given network.

## POST /ideas/discover

**Discover ideas for a keyword** (operationId: `discoverIdeas`)

Discovers content ideas for a keyword on a given network. This call is **synchronous** and can be slow — worst case around **2 minutes** while the upstream keyword tool responds. On success it returns the keyword summary (with `ideas_count`); read the discovered ideas themselves with `GET /ideas`.

**💳 Charges 15 credits — and it charges on *every* fruitful call, including a cached re-search of a keyword you have already discovered.** Discovery results are cached globally for three weeks; a repeat discover of the same keyword within that window returns the cached ideas **but still charges the full 15 credits**. A call is charged only when the search actually yields ideas (`ideas_count > 0`); a search that finds nothing is free. The charge and your remaining balance are reported in `meta.credits`.

**Do not blindly retry a timed-out discover.** The synchronous worst case (~2 minutes) exceeds many client and proxy timeouts, so your HTTP layer may give up *after* the server has already completed the discovery and charged you. There is **no idempotency key** on this endpoint: a retry that lands on the now-warm three-week cache is treated as a fresh fruitful discovery and **charges another 15 credits for the same logical search**. If a discover times out, reconcile first with the **free** `GET /ideas` (same `keyword` + `network`): if it now returns ideas, the discovery succeeded — do not retry.

If your balance is below 15 credits the request is rejected up front with `402` before any search runs.

### Request body

Required.

- `keyword` (string, required) — Required — the keyword or phrase to discover content ideas for (2–80 characters).
- `network` (string, required) — Required — the network the ideas are sourced for: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, or `tiktok`. Allowed values: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, `tiktok`.

Example request:

```json
{
  "keyword": "cold plunge benefits",
  "network": "youtube"
}
```

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": {
    "name": "cold plunge benefits",
    "slug": "cold-plunge-benefits",
    "network": "youtube",
    "ideas_count": 42,
    "searched_at": "2026-01-01T12:00:00.000000Z"
  },
  "meta": {
    "credits": {
      "cost": 15,
      "remaining": 885
    }
  }
}
```

### Errors

- `401` — Unauthenticated
- `402` — Insufficient credits. The request is authenticated and valid, but the account balance is too low — top up and retry.
- `403` — Forbidden — two distinct causes share this status: 1. **No active subscription** (code `SUBSCRIPTION-REQUIRED`, message "An active subscription is required.") — checked first by the active-subscription gate, before any ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `422` — Validation error
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).

## GET /ideas

**List ideas for a keyword** (operationId: `listIdeas`)

Returns a paginated, sortable list of the ideas discovered for a keyword. **Free — no credits charged.**

You can only list ideas for a keyword **you have previously discovered** via `POST /ideas/discover`; a `keyword`+`network` pair you never discovered responds with `403`. Sort with `sort` (default `-volume`).

### Parameters

- `keyword` (query, string, required) — The keyword to list ideas for — must be one you have previously discovered (2–80 characters). Example: `cold plunge benefits`
- `network` (query, string, required) — The network the keyword was discovered on: `google`, `google-trends`, `youtube`, `instagram`, `twitter`, `bing`, `pinterest`, or `tiktok`. Example: `youtube`
- `sort` (query, string|null) — Sort field, optionally prefixed with `-` for descending. One of `volume`, `cpc`, `competition`, `trend` (or their `-` variants). Defaults to `-volume` (highest search volume first). Example: `-volume`

### Response `200`

```json
{
  "message": "Success.",
  "status": 200,
  "data": [
    {
      "name": "Cold plunge for beginners: a 30-day guide",
      "volume": 18100,
      "cpc": 0.42,
      "competition": 0.35,
      "competition_label": "LOW",
      "trend": "up",
      "trends": [
        {
          "month": "2025-11",
          "value": 14800
        },
        {
          "month": "2025-12",
          "value": 16200
        },
        {
          "month": "2026-01",
          "value": 18100
        }
      ],
      "updated_at": "2026-01-01T12:00:00.000000Z"
    },
    {
      "name": "Are cold plunges worth it? The science, explained",
      "volume": 9800,
      "cpc": 0.42,
      "competition": 0.52,
      "competition_label": "MEDIUM",
      "trend": "flat",
      "trends": [
        {
          "month": "2025-11",
          "value": 14800
        },
        {
          "month": "2025-12",
          "value": 16200
        },
        {
          "month": "2026-01",
          "value": 18100
        }
      ],
      "updated_at": "2026-01-01T12:00:00.000000Z"
    }
  ]
}
```

### Errors

- `401` — Unauthenticated
- `403` — Forbidden — two distinct causes share this status: 1. **No active subscription** (code `SUBSCRIPTION-REQUIRED`, message "An active subscription is required.") — checked first by the active-subscription gate, before any ownership or precondition logic. The v2 public API has no free tier. 2. **Authorization / ownership or precondition failure** — the subscription is active but the action is not allowed: the resource belongs to another account, or a render precondition is unmet (e.g. `A script is required before rendering.`, `Voice was not provided.`, the video is busy, or storage is full).
- `405` — The URL exists but not for this HTTP method — e.g. `GET /presets/faceless/{preset}`, which is only defined for `PATCH` and `DELETE`. Check the verb against the endpoint reference; the `Allow` header lists the methods this path accepts.
- `409` — Email address not verified. The token is valid and the account exists, but the user has not confirmed their email address. This gate fronts every v2 endpoint. Have the user complete verification, then retry.
- `422` — Validation error
- `429` — Rate limit exceeded — requests are limited per API token (30 per minute by default). The response carries `Retry-After` (seconds to wait) and `X-RateLimit-Reset` (Unix timestamp when the window resets); back off until then and retry. Successful responses include `X-RateLimit-Limit` and `X-RateLimit-Remaining` so you can pace requests proactively.

Error shapes and examples are shared across endpoints — see the **Error responses** section in [getting-started](getting-started.md).
