Agent API (beta)
The Agent API is in beta. Endpoints, parameters, and response fields can change or be removed without a deprecation period, and the /v1 in the path is not a stability promise. Only build on it if you can update your integration quickly.
If your agent supports MCP, connect it through the MCP server instead. MCP clients read the current tool list and argument schemas each time they connect, so they adapt to changes that would break a hard-coded HTTP call.
The Agent API gives scripts and AI agents HTTP access to the same data and actions as the MCP server. The two share authorization, rate limits, previews, and the action audit. Use the HTTP API when you can't use MCP, for example in a scheduled job or a webhook receiver.
The API is served at https://api.spenddaddy.app/api/v1. Its OpenAPI schema is at https://api.spenddaddy.app/api/v1/openapi.json.
App IDs are immutable, case-sensitive 12-character random strings, for example
app000000042 (synthetic). Use the id returned by list_apps or
GET /api/v1/apps in every app path, proposal, and MCP argument. Integer
database keys are no longer accepted. Existing app scopes remain valid
internally; clients must refresh IDs and discard old pagination cursors.
Authentication
Create a key in Settings → Agents and send it as a bearer token:
Authorization: Bearer <agent-key>
Only organization owners can create keys. A key can be limited to one app and can have an expiry date. A key that should commit Apple Ads changes also needs the Permit mutations under owner policy option, which requires the app limit. Keys created before this option existed are read-only.
Read endpoints
| Endpoint | Returns |
|---|---|
GET /apps | Apps visible to the key and the key's capabilities |
POST /apps/{app_id}/keywords:lookup | Acquisition economics for a batch of keyword texts |
GET /apps/{app_id}/acquisition | Campaign, ad group, keyword, or search term comparisons |
GET /apps/{app_id}/funnel | Install cohort funnel |
GET /apps/{app_id}/economics | D7, D30, D60, and D90 cohort revenue curves with maturity |
GET /apps/{app_id}/placement-outlook | Provisional placement forecast |
GET /apps/{app_id}/health | Data health for the last 1 to 365 days |
GET /apps/{app_id}/policy | The owner's execution policy and the authority it grants |
GET /apps/{app_id}/recommendations | Recommendation list |
GET /recommendations/{id} | One recommendation with its evidence |
GET /apps/{app_id}/actions | Action audit list |
GET /actions/{id} | One action with its preview evidence and evaluations |
GET /events | Committed events since a cursor |
GET /webhooks/health | Delivery status counts for event webhooks |
Acquisition, funnel, and economics reads accept inclusive start_date and end_date values up to 365 days apart. The acquisition and action lists are paginated with cursors. If the metrics revision changes while you page, the API returns snapshot_changed; start again from the first page.
Economics and evaluations
economics returns daily cohort curves at app scope, or at campaign, ad group, or keyword scope with entity_type and entity_id. Proceeds are marked partial until SpendDaddy can verify that revenue coverage is complete. While they are partial, contribution and observed payback are null.
placement-outlook is a provisional forecast. It lists its validation limits, and SpendDaddy never uses it to approve an automatic change.
An action's status tracks execution. Its evaluation compares metrics before and after the change and does not claim that the action caused the difference. Each re-evaluation adds a revision; earlier revisions are kept. The action also keeps the evidence its preview showed, even if metrics were revised later.
Outcome annotations
POST /apps/{app_id}/outcome-annotations records a product, price, or paywall change that may explain a shift in outcomes:
{
"kind": "paywall",
"occurred_at": "2026-09-20T09:00:00Z",
"note": "New annual plan paywall shipped to 50% of users."
}
occurred_at must include a timezone, and note is limited to 500 characters. The annotation is saved as proposed and needs an owner's confirmation.
Events
Poll GET /events for readiness, recommendation, action, and evaluation changes. The MCP list_events tool returns the same records.
curl -H 'Authorization: Bearer <agent-key>' \
'https://api.spenddaddy.app/api/v1/events?after=<cursor>&limit=100'
The response contains items, next_cursor, and has_more. Store next_cursor and pass it as after on the next poll, even when has_more is false. limit can be up to 200.
| Event type | Sent when |
|---|---|
data.readiness_changed | An app's data readiness changes |
recommendation.created | SpendDaddy creates a recommendation |
action.status_changed | An action's execution status changes |
action.evaluation_updated | An action gets a new evaluation revision |
Events are kept for 90 days. A cursor is bound to the key's organization and app scope. If the API returns cursor_expired, reload the resources you track and poll again without a cursor.
Event webhooks
Owners can push the same events to an HTTPS endpoint from Settings → API & webhooks → Event webhooks. The SpendDaddy operator must allow the destination's origin first.
Deliveries are at least once, so deduplicate by X-SpendDaddy-Event-ID. To verify a delivery:
- Read
X-SpendDaddy-Timestampand reject it if it is more than five minutes from your clock. - Compute HMAC-SHA256 with the signing secret over
<timestamp>.<raw body bytes>. - Compare the lowercase hex result with
X-SpendDaddy-Signatureusing a constant-time comparison.
Return a 2xx status to acknowledge a delivery. A 410 disables the webhook. SpendDaddy retries 429, 5xx, and network errors with backoff, then marks the delivery failed. An owner can replay a failed delivery from Settings; the replay keeps the original event ID.
Actions
An owner can enable an execution policy for four single-target Apple Ads actions:
keyword_bidkeyword_statuscampaign_budgetcampaign_status
The policy sets which campaigns and action types are allowed, a currency, amount limits, a daily action count, and an expiry. A mutation key alone cannot change spend: every commit also checks the policy and the organization's mutation guardrail. Budget limits cap the daily budget an agent can set, not what Apple Ads actually spends.
Other changes, such as removing a negative keyword, stay in the dashboard for a person to apply.
Policy and autopilot
The same policy also decides what the autopilot may do. The autopilot acts on the daily review's recommendations, not on requests from an agent, but it passes the same checks: policy scope and limits, daily action count, expiry, the organization's Apple Ads mutation guardrail, and a check of the live Apple Ads state.
Owners set the policy under Settings → Agents. Autopilot mode has three options:
- Off: the autopilot does nothing.
- Shadow: records what it would change and changes nothing. This is the default when a policy is enabled.
- Live: makes Apple Ads changes on its own inside the policy and tells the owner on Telegram.
Turning on Live needs an explicit confirmation ("Turn on live autopilot?") and a save, and is never chosen automatically. In Live mode each raise gets a guard, and the Telegram message "Done … Guard armed." has Undo and Details buttons. The Autopilot track record under the policy shows what the autopilot picked, how often the owner agreed, and the observed outcomes. The mode does not affect keys: a mutation key still needs the policy to commit. The autopilot does not use your agent keys. See Autopilot.
GET /apps/{app_id}/policy returns the policy, including mode, in its agent_execution object. The mode is read-only over the API: owners change it in the dashboard, and API keys cannot.
Preview and commit
curl -H 'Authorization: Bearer <agent-key>' -H 'Content-Type: application/json' \
-d '{"type":"keyword_bid","app_id":"app000000001","keyword_id":42,"amount":"1.10","currency":"USD"}' \
'https://api.spenddaddy.app/api/v1/actions/preview'
curl -H 'Authorization: Bearer <agent-key>' -H 'Content-Type: application/json' \
-H 'Idempotency-Key: <unique-operation-key>' -d '{"preview_id":123}' \
'https://api.spenddaddy.app/api/v1/actions/commit'
A preview shows the economic evidence and source-quality warnings for the change. D30 proceeds may be null when revenue coverage is unverified. Incomplete economics do not block a commit that the owner's policy allows.
Retry and reconcile
Keep the preview ID and idempotency key. If a commit response is lost, retry with the same pair. SpendDaddy returns the original result and does not write to Apple Ads again, whether the first attempt came over HTTP or MCP.
If an action is pending, call POST /actions/{id}/reconcile. Reconciliation reads the current state from Apple Ads and never repeats the change. While an action is pending, no agent can commit another change for that app.
Errors
Errors return error.code and error.message. Common codes:
| Code | Meaning |
|---|---|
unauthorized | The key is missing, invalid, expired, or revoked. |
policy_disabled | The app's execution policy is off or has expired. |
mutation_guardrail_disabled | The organization does not allow Apple Ads changes. An owner can allow them in Settings → Agents → Apple Ads changes. |
preview_expired | Create a new preview before committing. |
snapshot_changed | Metrics changed during pagination. Restart from the first page. |
ambiguous_prior_action | Another action for the app is still pending. Reconcile it first. |
cursor_expired | The event cursor is older than 90 days. Reload and poll without a cursor. |
rate_limited | Wait for the number of seconds in the Retry-After header. |