GET /v1/projects/:projectId/recommendations
Agent-ready suggestions Layers has surfaced about this project — what to promote, what to refresh, where engagement is dropping.
/v1/projects/{projectId}/recommendations- 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.
projectIdstring (UUID)requiredProject to list recommendations for.
kindstring[]optionalRestrict to one or more kinds. Repeat for multiple.One of:value_propositions,refresh_fatiguedstatusstring[]optionalFilter by acknowledgement state.One of:open,acknowledged,dismissed,acted_onminConfidencenumberoptionaldefault: 0Keep rows with confidence >= minConfidence. 0 to 1.sortstringoptionaldefault: confidence_descSort order.One of:confidence_desc,created_desccursorstringoptionalOpaque pagination cursor.limitnumberoptionaldefault: 25Page 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
{
"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
kindis 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).confidenceis a0–1score based on signal strength and evidence volume.>= 0.7is "act on this";< 0.5is "show it to a human."subject.typenames the entity the recommendation is about.subject.idplussubject.typeis enough to render a link to the relevant surface in your UI.rationaleis a plain-language explanation intended for humans. Do not pattern-match on it — it is regenerated per language and phrased differently over time.evidenceis stable perkind. Schema-check againstkind, not across kinds.suggestedActionis the machine-executable version.endpointis a relative path; prependhttps://api.layers.combefore calling.bodyalready 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
subjectcan 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.bodyis 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-metricsbefore acting if the recommendation is cost-impacting. The evidence snapshot can lag live data by a few hours.
See also
GET /v1/projects/:projectId/ads-content— underlying signal forrefresh_fatigued- Publish to learn — the feedback loop these recommendations feed