Atribu
API Reference

Recommendations

The AI media buyer's suggestions, how to apply one safely, and why the apply is idempotent.

Scale a winner, pause an underperformer, reallocate budget, refresh a creative before fatigue lands. GET /api/v1/recommendations lists the open ones for the key's profile; the workspace-wide view is the MCP tool list_workspace_recommendations.

All list parameters are comma-separated and validated strictly — an unknown value is a 400, never a silent drop. A filter that silently drops an unknown value returns a different set than the caller asked for and says nothing.

What a recommendation is based on

A recommendation attests; it does not predict. Atribu makes one because an ad has already closed sales it could attribute — at least 3 of them — and its cost per sale is at least 10% away from that client's own median. Every number in rationale.evidence is reproducible by the advertiser: ad spend from Meta Ads Manager, the sale count and value from their payment provider.

A "sale" is a cash payment (revenue_type = 'cash'), never a lead. The cash kinds carry window_days, attributed_cash_outcomes, cost_per_cash_outcome, profile_median_cost_per_cash_outcome, cost_delta_pct (negative = cheaper than the median), spend and currency. ad_name is on the row — render it, never target.ad_id.

rationale.text is OPTIONAL commentary written by the arbiter, not the argument. Compose the sentence a reader sees from rationale.evidence.

expected_impact_dollars is arithmetic on that ad's own observed unit economics over the next 7 days, under the stated assumption that extra budget performs like the window did. Phrase it with "if", never "expected", "forecast" or "at risk".

Expect the list to be short. The three-sale floor is a minimum-evidence rule, not a tuned threshold, and the binding constraint on how many recommendations exist is cash-attribution coverage, not the criterion.

Applying one

Endpoint
POST /api/v1/recommendations/{id}/apply

Executes through the meta-actions layer: one write spine, one audit trail (meta_action_log), one rollback system. There is no queue and nothing to poll — the response says what happened.

`Idempotency-Key` is required

1–256 characters. Leg-level dedup is automatic (the executor derives its keys from the recommendation id), so retrying an already-applied recommendation answers 200 with replayed: true rather than double-spending a budget.

GET /api/v1/recommendations/{id}/diagnose answers why did this recommendation do what it did — the read to make before explaining an applied change to a client, and the one to make when it did not do what you expected.

POST /api/v1/recommendations/{id}/dismiss closes one without applying it.

When a human should approve first

require_approval: true on the apply mints an approve hand-off carrying the exact preview, and answers 202 with {status: "awaiting_approval", handoff: {id, url, expires_at}} having written nothing. The human opens the URL signed out, sees what changes and on which account, and presses Approve or Reject; your agent polls GET /api/v1/handoffs/{id} for the same result payload the direct call returns.

Approval is OR'd, never AND'd

It is required when the principal is not a workspace owner/admin, or whenever any caller passes require_approval: true. The second is the agency case: an owner/admin token deliberately routing every Meta write on a client's account past the client. Rejecting or expiring leaves no partial write, and the pending action's own row is the audit record — status plus result.outcome ∈ approved | rejected | expired | failed.

Over MCP, apply_recommendation's confirm mints the same object under the same rule.

Until then, the MCP tool's preview → dry_run → confirm ceremony plus the workspace-level mcp_writeback_enabled gate is the safety boundary — see Write-back.

MethodPathWhat it does
GET/api/v1/recommendationsList open recommendations for this key's profile
POST/api/v1/recommendations/{id}/applyApply a recommendation (inline, terminal response)
GET/api/v1/recommendations/{id}/diagnoseWhy did this recommendation do what it did
POST/api/v1/recommendations/{id}/dismissDismiss a recommendation

Generated from openapi.json. The full request and response schema for every operation is in the OpenAPI document.

Next steps

On this page