# GET /v1/projects/:projectId/recommendations (/docs/api/reference/recommendations/list-recommendations)



<Endpoint method="GET" path="/v1/projects/{projectId}/recommendations" auth="Bearer" scope="metrics:read" phase="1" />

Returns a ranked list of suggestions Layers has inferred about the project — "promote this creative," "refresh this fatigued ad," "this value prop is underused in your generation prompts." Each item has a stable `kind`, a `subject` the recommendation is about, a human-readable `rationale`, and a `suggestedAction` payload an agent can act on without further reasoning.

This is the agent-ready surface. If you are building an autonomous loop — promote winners, retire losers, refresh creatives on a cadence — this endpoint is the one you poll. The `suggestedAction` shape is designed to be forwardable to the mutation endpoint named in `suggestedAction.endpoint` with minimal translation.

<Callout type="warn">
  Two recommendation kinds ship today: `value_propositions` and `refresh_fatigued`. More kinds are planned as the underlying signals mature. Filter by `kind` if you want to pin your agent to a stable subset.
</Callout>

<Parameters
  title="Path"
  rows="[
  { name: 'projectId', type: 'string (UUID)', required: true, description: 'Project to list recommendations for.' },
]"
/>

<Parameters
  title="Query"
  rows="[
  { name: 'kind', type: 'string[]', description: 'Restrict to one or more kinds. Repeat for multiple.', enum: ['value_propositions', 'refresh_fatigued'] },
  { name: 'status', type: 'string[]', description: 'Filter by acknowledgement state.', enum: ['open', 'acknowledged', 'dismissed', 'acted_on'] },
  { name: 'minConfidence', type: 'number', description: 'Keep rows with confidence >= minConfidence. 0 to 1.', default: '0' },
  { name: 'sort', type: 'string', description: 'Sort order.', enum: ['confidence_desc', 'created_desc'], default: 'confidence_desc' },
  { name: 'cursor', type: 'string', description: 'Opaque pagination cursor.' },
  { name: 'limit', type: 'number', description: 'Page size, 1–100.', default: '25' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/recommendations?status=open&sort=confidence_desc&limit=10" \
      -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/recommendations?status=open&limit=10`,
      { headers: { Authorization: `Bearer ${apiKey}` } },
    );
    const { items } = await res.json();

    // Dispatch each recommendation to its suggested endpoint.
    for (const rec of items) {
      if (rec.confidence < 0.7) continue;
      await fetch(`https://api.layers.com${rec.suggestedAction.endpoint}`, {
        method: rec.suggestedAction.method,
        headers: {
          Authorization: `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(rec.suggestedAction.body),
      });
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import httpx

    r = httpx.get(
        f"https://api.layers.com/v1/projects/{project_id}/recommendations",
        params={"status": "open", "limit": 10},
        headers={"Authorization": f"Bearer {api_key}"},
    )
    items = r.json()["items"]
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="OK">
  ```json
  {
    "items": [
      {
        "recommendationId": "rec_01HXK2...",
        "kind": "refresh_fatigued",
        "status": "open",
        "confidence": 0.86,
        "createdAt": "2026-04-18T10:00:00Z",
        "subject": {
          "type": "ads_content",
          "id": "adc_01HXC9...",
          "label": "Before-and-after latte tutorial"
        },
        "rationale": "CPA rose 41% over the last 7 days while impressions held steady — classic fatigue. Top-line ROAS is still positive but trending down.",
        "evidence": {
          "cpaChange7d": 0.41,
          "impressions7d": 82100,
          "conversions7d": 18
        },
        "suggestedAction": {
          "endpoint": "/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/content/clone-from-post",
          "method": "POST",
          "body": {
            "sourcePlatformPostId": "pp_01HXC8...",
            "variations": 3
          }
        }
      },
      {
        "recommendationId": "rec_01HXK3...",
        "kind": "value_propositions",
        "status": "open",
        "confidence": 0.74,
        "createdAt": "2026-04-18T10:00:00Z",
        "subject": {
          "type": "project",
          "id": "prj_01HX9Y7K...",
          "label": "Acme Coffee"
        },
        "rationale": "'Same-day rewards' drove 3.2x the conversion rate of your other value props last month but appears in only 8% of generated creatives.",
        "evidence": {
          "valueProp": "Same-day rewards",
          "uplift": 3.2,
          "coverage": 0.08
        },
        "suggestedAction": {
          "endpoint": "/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB",
          "method": "PATCH",
          "body": {
            "brand": {
              "valuePropositionsEmphasis": ["Same-day rewards"]
            }
          }
        }
      }
    ],
    "nextCursor": null
  }
  ```
</Response>

## Reading a recommendation [#reading-a-recommendation]

* `kind` is stable and safe to switch on. A new kind is a non-breaking change; a renamed kind is a breaking change (handled via deprecation in [Errors](/docs/api/operational/errors)).
* `confidence` is a `0–1` score based on signal strength and evidence volume. `>= 0.7` is "act on this"; `< 0.5` is "show it to a human."
* `subject.type` names the entity the recommendation is about. `subject.id` plus `subject.type` is enough to render a link to the relevant surface in your UI.
* `rationale` is a plain-language explanation intended for humans. Do not pattern-match on it — it is regenerated per language and phrased differently over time.
* `evidence` is stable per `kind`. Schema-check against `kind`, not across kinds.
* `suggestedAction` is the machine-executable version. `endpoint` is a relative path; prepend `https://api.layers.com` before calling. `body` already has the right shape for the target endpoint.

## Acting on a recommendation [#acting-on-a-recommendation]

Recommendations are passive — reading the list does not change anything. Execute `suggestedAction.endpoint` with the suggested body to act on one, then record the outcome in your own system. A partner-facing acknowledge/dismiss endpoint is on the roadmap but not shipped yet; track recommendation status in your own pipeline for now.

## Notes [#notes]

* The same `subject` can generate multiple open recommendations of different kinds. Dedupe by `(kind, subject.id)` if you only want one action per subject.
* Recommendations refresh on a 6-hour cadence from the same scoring pipeline that updates [`ads-content`](/docs/api/reference/metrics/ads-content). Do not expect minute-by-minute reactivity.
* `suggestedAction.body` is a suggestion, not a contract — your agent is free to substitute its own parameters before calling the target endpoint.
* Cross-check against [`GET /v1/projects/:projectId/ads-metrics`](/docs/api/reference/ads/ads-metrics) before acting if the recommendation is cost-impacting. The evidence snapshot can lag live data by a few hours.

## See also [#see-also]

* [`GET /v1/projects/:projectId/ads-content`](/docs/api/reference/metrics/ads-content) — underlying signal for `refresh_fatigued`
* [Publish to learn](/docs/api/guides/publish-to-learn) — the feedback loop these recommendations feed
