Layers

GET /v1/projects/:projectId/recommendations

Agent-ready suggestions Layers has surfaced about this project — what to promote, what to refresh, where engagement is dropping.

View as Markdown
GET/v1/projects/{projectId}/recommendations
Phase 1stable
Auth
Bearer
Scope
metrics:read

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.

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.

Path
  • projectId
    string (UUID)required
    Project to list recommendations for.
Query
  • kind
    string[]optional
    Restrict to one or more kinds. Repeat for multiple.
    One of: value_propositions, refresh_fatigued
  • status
    string[]optional
    Filter by acknowledgement state.
    One of: open, acknowledged, dismissed, acted_on
  • minConfidence
    numberoptionaldefault: 0
    Keep rows with confidence >= minConfidence. 0 to 1.
  • sort
    stringoptionaldefault: confidence_desc
    Sort order.
    One of: confidence_desc, created_desc
  • cursor
    stringoptional
    Opaque pagination cursor.
  • limit
    numberoptionaldefault: 25
    Page size, 1–100.

Example request

curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/recommendations?status=open&sort=confidence_desc&limit=10" \
  -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."
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),
  });
}
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"]

Response

200OK
{
  "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
}

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).
  • 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

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

  • 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. 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 before acting if the recommendation is cost-impacting. The evidence snapshot can lag live data by a few hours.

See also

On this page