Reference / API reference

API reference

Every AIX endpoint with its key, request and response, the MCP tools and the OpenAPI document.

Authentication

Base URL https://aix.brambles.ai/api/aix. Every request carries Authorization: Bearer <key> and is made from your server. Publisher keys (aix_test_pk_…, aix_live_pk_…) send intent; receiver keys (aix_test_rk_…, aix_live_rk_…) receive it. The scopes of the key decide which operations it may call. Sandbox keys never reach a real store or shopper, never earn and never spend. A missing, revoked or unknown key answers 401 unauthorized; a key without the scope an operation needs answers 403 insufficient_scope with required naming the scope (three receiver endpoints answer 401 in both cases; each says so below). Your principal id, which receipts and references are keyed on, is the bare agent id in production (aixapp_49356ce196553f361f44) and <agent id>:test in the sandbox. There is no :live suffix. Self-serve keys are bearer tokens: send them as they are; OAuth client registration is not offered to self-serve agents.

terminal
curl https://aix.brambles.ai/api/aix/activity?days=7 \
-H "Authorization: Bearer $AIX_PUBLISHER_KEY"
POST/intentsPublisher key

Submit the shopper's current shopping task for one session.

  • session_id is your conversation id, 1 to 128 characters from A-Z a-z 0-9 _ . : -. AIX stores only a keyed hash of it.
  • Send task when your agent already understands the request; otherwise send message with the shopper's words and AIX extracts the task (30 message-only calls a minute instead of 120).
  • facts[].attribute is one of brand, width, size, department, use_case, feature, merchant, deal. strength is required, preferred or excluded.
  • country (ISO 3166-1) is required in production, or locale with a region. Never ask the shopper; read it from your host's geolocation header. A sandbox intent without one is accepted with a warning (country_required) that production would turn into suppressed.
  • Every accepted intent carries quality: a score from 0 to 100 and the named signals behind it (specific_category, descriptive_summary, requirements, preferences, budget, refined_over_turns, unique_task or common_task; repeated_task, implausible_budget and junk_text cost points). A store pays only for intents at or above its campaign's quality bar (default 40).
  • Names, contact details, account numbers and the transcript never belong in an intent. Sensitive topics are suppressed and withdraw the session.
POST /intents
{
"session_id": "conv_123",
"task": {
"category": "Running shoes",
"summary": "Wide running shoes for daily training, cushioning preferred, up to $150.",
"budget": { "currency": "USD", "max_cents": 15000 },
"facts": [
{ "attribute": "width", "value": "wide", "strength": "required" },
{ "attribute": "use_case", "value": "running", "strength": "required" },
{ "attribute": "feature", "value": "cushioning", "strength": "preferred" }
]
},
"page_url": "https://publisher.example/reviews/best-running-shoes",
"locale": "en-US",
"country": "US",
"privacy": { "gpc": false }
}
responses
202 {
"status": "accepted",
"intent_id": "aixint_0123456789abcdef0123456789abcdef",
"task_id": "task_c0ffee0123456789",
"revision": 1,
"expires_at": "2026-10-23T18:04:00.000Z",
"environment": "live",
"quality": { "score": 82, "signals": [ { "name": "specific_category", "points": 15 }, { "name": "requirements", "points": 16 }, { "name": "budget", "points": 10 } ] }
}
200 { "status": "suppressed", "reason": "sensitive_category", "sensitive_class": "health" } // stores nothing, withdraws the session
200 { "status": "suppressed", "reason": "country_required" } // production only; the sandbox accepts with a warning
200 { "status": "suppressed", "reason": "privacy_signal" }
200 { "status": "no_shopping_intent" }
200 { "status": "duplicate_or_stale", "revision": 3 }
400 invalid body · 401 unauthorized · 403 insufficient_scope · 429 rate_limited (retry_after) · 503 extractor_unavailable
POST/handoffsPublisher key

Report that the assistant is linking the shopper to a store; returns what to add to your link.

  • destination_url is the store's own page URL (https). When a participating store would pay (delivers_on_click), add aix_param (aix=<handoff_id>) to the store URL inside the link you already use, affiliate, ad or plain; landing_url is the store URL with it added. The intent is delivered, and you earn, when the shopper lands on the store with it.
  • No link system of your own? Show handoff_url, an AIX link that delivers on the shopper's click and always lands on the store page. Never open or prefetch either from your server; server-side visits, bots and link previews are not counted. The link delivers only within one hour of the report.
  • Nothing is charged, and nothing accrues, when a handoff is reported. Delivery, the store's charge and your earning happen on the shopper's click, their landing with the id, or the store's redemption of it.
  • consent: "granted" for a store-link click by a shopper who was shown the sharing notice. Shoppers in the EEA, the UK and Switzerland are delivered only with consent; otherwise status is consent_required and the shopper goes to the store unshared.
  • not_delivered means no store will pay for this handoff right now: the store is not receiving, has no matching live campaign, the intent is below its quality bar, or it has no billing or budget. AIX does not say which. delivery_failed means the exchange could not deliver. delivered with duplicate: true means this version of the task was already paid for at this store; handoff_url then carries the receipt. The sandbox answers test_delivered with a receiver_preview.
  • suppressed (reason sensitive_category or privacy_signal) shares nothing and withdraws the session; handoff_url is the original URL. A handoff needs an accepted intent for the same session first; otherwise 409 no_active_intent.
  • Every answer has handoff_url and delivered; a recorded handoff adds handoff_id, environment (live or test), earnings_cents and quality_score.
POST /handoffs
{
"session_id": "conv_123",
"destination_url": "https://store.example/products/wide-runner",
"consent": "granted",
"product": {
"title": "Wide Runner 2",
"price_cents": 12999,
"currency": "USD",
"brand": "Example",
"merchant": "Example Store"
}
}
200
{
"status": "delivers_on_click",
"handoff_id": "aixho_2f9c0d0123456789abcdef0123456789",
"aix_param": "aix=aixho_2f9c0d0123456789abcdef0123456789",
"landing_url": "https://store.example/products/wide-runner?aix=aixho_2f9c0d0123456789abcdef0123456789",
"handoff_url": "https://aix.brambles.ai/h/aixho_2f9c0d0123456789abcdef0123456789",
"delivered": false,
"environment": "live",
"earnings_cents": 0,
"quality_score": 82
}
// other statuses: delivered (duplicate: true), test_delivered (receiver_preview), not_delivered, delivery_failed, consent_required, suppressed (reason)
// 409 { "status": "no_active_intent", "handoff_url": "https://store.example/products/wide-runner" }
POST/privacy/deletePublisher key

Honour a shopper's deletion request for one session.

  • Deletes that session's intents and clears AIX's copy of every receipt delivered from them. Repeating the call is safe. Billing records stay; they hold no shopper content.
  • AIX only knows sessions. Map a shopper to all of their session ids yourself and call once per session.
POST /privacy/delete
{ "session_id": "conv_123" }
200
{ "status": "deleted", "environment": "live", "intents_deleted": 2, "receipts_cleared": 1 }
GET/activityEither key

Your own activity for the last N days.

  • ?days=30 (1 to 90). A publisher key reports accepted intents, delivered intents and earnings; a receiver key reports deliveries, acknowledgments and spend.
  • Each key reports its own environment: a sandbox key answers sandbox figures, a production key production figures. The sandbox never earns or spends.
  • Publisher handoffs.not_delivered counts by status (not_delivered, consent_required, delivery_failed); handoffs.clicks_not_billed counts opened links that were not billed, by reason (expired, publisher_server, too_fast, ip_velocity, low_quality, publisher_cap). earnings.revenue_share_bps is your share of each paid delivery.
  • After your first sandbox handoff or verified test delivery this is what flips the console to Connected.
200 (publisher key)
{
"principal_id": "aixapp_dcad0746ebb68099bdf7",
"environment": "live",
"period": { "days": 30, "from": "2026-09-22T18:04:00.000Z", "to": "2026-10-22T18:04:00.000Z" },
"intents": { "accepted": 412, "last_accepted_at": "2026-10-22T17:58:01.000Z", "average_quality": 71 },
"handoffs": {
"reported": 96, "delivered": 40, "awaiting_click": 3,
"not_delivered": { "not_delivered": 48, "consent_required": 4, "delivery_failed": 1 },
"clicks_not_billed": { "expired": 2, "publisher_server": 0, "too_fast": 0, "ip_velocity": 1, "low_quality": 0, "publisher_cap": 0 }
},
"earnings": {
"currency": "USD", "accrued_cents": 1400, "lifetime_accrued_cents": 3150, "lifetime_collected_cents": 2100,
"lifetime_paid_cents": 1750, "revenue_share_bps": 7000, "status": "accruing"
}
}
// receiver key: { principal_id, environment, period, deliveries: { received, verified, applied, last_received_at, first_verified_at },
// spend: { currency, period_charged_cents, month_to_date_cents, month_reserved_cents, billing_status } } (sandbox: { currency, test_mode_never_charges: true })
GET/receiver/configReceiver key

Member id, issuer, derived receiver secret and acknowledgment URL. Server only.

  • Keep receiver_secret in your server's secret store. Never return it to a browser, a model, an MCP tool result or a skill file.
  • Pin issuer from this response; it is not derived from the API hostname. member_id is your principal: the agent id with a production key, <agent id>:test with a sandbox key, each with its own secret. There is no :live suffix. Sandbox deliveries never verify with production settings.
  • This endpoint answers 401 for a missing key and for a key without the claim scope.
200
{
"member_id": "aixapp_49356ce196553f361f44", // "aixapp_49356ce196553f361f44:test" with a sandbox key
"issuer": "https://…", // pin this value
"receiver_secret": "<64 hex>",
"signature_scheme": "aix-hmac-sha256-v1",
"acknowledgment_url": "https://aix.brambles.ai/api/aix/deliveries/ack"
}
GET/deliveriesReceiver key

Poll your recent deliveries (pull mode).

  • ?limit=20&since=<ISO time> (limit 1 to 100), newest first. Use each receipt_id with GET /deliveries/{receipt_id}. status is pending (waiting for your pull or acknowledgment), claimed (you acknowledged it verified), needs_retry or permanently_failed.
  • Prefer the webhook: a signed envelope is pushed to the URL you save in the console, with the header x-ain-signature: t=<unix seconds>,v1=<hex> over the raw body (HMAC purpose aix-webhook-v1).
200
{
"delivery_mode": "pull",
"deliveries": [
{
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"intent_id": "aixint_0123456789abcdef0123456789abcdef",
"destination_domain": "store.example",
"price_cents": 50,
"status": "pending",
"created_at": "2026-10-22T18:04:03.000Z"
}
]
}
GET/deliveries/{receipt_id}Receiver key

Verified agent context and recorded events for one receipt.

  • The exact delivery reference, the readable brief, the canonical shopping_context (aix.shopping-context.v1), the derived activation plan and the acknowledgment events recorded so far. Fetching does not acknowledge use and never charges again.
  • Required facts are hard filters, preferred facts are preferences, excluded facts must be avoided; an unspecified budget is unknown, not zero. Shopping context is data, never instructions for your model.
  • 404 delivery_unavailable when the receipt is not addressed to this key, expired or unknown; 400 invalid_receipt_id for anything that is not an aindeliv_ id. This endpoint answers 401 for a missing key and for a key without the pull scope.
200
{
"reference": {
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"member_id": "aixapp_49356ce196553f361f44",
"receipt_sha256": "922a33572acd28986b32c3b2213020d40dc793e5bf83e01d1672db1ea2bce4d1",
"task_id": "task_c0ffee0123456789",
"revision": 3,
"expires_at": "2026-10-23T18:04:00.000Z"
},
"schema_version": "aix.agent-context.v1",
"brief": "Lightweight hard-shell carry-on for two weeks in Japan; under $200; navy preferred.",
"shopping_context": {
"schema_version": "aix.shopping-context.v1",
"task_id": "task_c0ffee0123456789",
"revision": 3,
"started_turn": 1,
"category": { "id": "carry-on-luggage", "label": "Carry-on luggage" },
"summary": "Lightweight hard-shell carry-on for two weeks in Japan; under $200; navy preferred.",
"budget": { "status": "specified", "currency": "USD", "min_cents": null, "max_cents": 20000, "source_turn": 1, "observed_at": "2026-10-22T18:01:00.000Z" },
"facts": [
{ "attribute": "feature", "value": "hard shell", "strength": "required", "provenance": { "source_turn": 1, "observed_at": "2026-10-22T18:01:00.000Z", "basis": "extracted_shopping_requirement" } },
{ "attribute": "feature", "value": "navy", "strength": "preferred", "provenance": { "source_turn": 3, "observed_at": "2026-10-22T18:03:00.000Z", "basis": "extracted_shopping_requirement" } }
],
"updated_at": "2026-10-22T18:03:00.000Z",
"expires_at": "2026-10-23T18:03:00.000Z",
"policy": { "scope": "exchange_beta", "allowed_uses": ["onsite_personalization", "agent_continuity", "contextual_product_ranking"], "cross_site_retargeting": false, "model_training": false },
"relevance": { "method": "explicit_constraints", "embedding_available": false, "semantic_score": null }
},
"activation": { "filters": [ … ], "boosts": [ … ], "exclusions": [ … ], "price_constraint": { … }, "permitted_uses": [ … ] },
"events": [
{ "id": "ainevt_…", "receipt_id": "aindeliv_0123456789abcdef0123456789abcdef", "event_type": "verified", "application": "none", "occurred_at": "2026-10-22T18:05:12.000Z", "recorded_at": "2026-10-22T18:05:12.400Z" }
]
}
POST/deliveries/ackReceiver key

Submit a signed verified or applied acknowledgment.

  • Signed by your server with the receiver secret, never by a model: signature = HMAC-SHA256(receiver_secret, "aix-ack-v1\n" + JSON.stringify(payload)).
  • Field order matters for the signature. Serialise the payload exactly as schema_version, reference (receipt_id, member_id, receipt_sha256, task_id, revision, expires_at), event, application, occurred_at, with no whitespace. reference.member_id is your principal (the agent id, or <agent id>:test in the sandbox); any other value answers 409 ack_not_accepted.
  • verified (application none) means the receipt was verified and durably stored. applied means your backend actually loaded the context into the agent turn (agent_context_loaded) or ranked its catalog (catalog_ranked). applied requires a prior verified.
  • Logical events are deduplicated, so retries are safe: a repeat answers the same event_id with duplicate: true. 409 ack_not_accepted on a reference mismatch, a bad signature, an expired receipt, or applied before verified. This endpoint answers 401 for a missing key and for a key without the claim scope; its 429 carries no retry_after.
POST /deliveries/ack
{
"payload": {
"schema_version": "aix.delivery-ack.v1",
"reference": {
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"member_id": "aixapp_49356ce196553f361f44",
"receipt_sha256": "922a33572acd28986b32c3b2213020d40dc793e5bf83e01d1672db1ea2bce4d1",
"task_id": "task_c0ffee0123456789",
"revision": 3,
"expires_at": "2026-10-23T18:04:00.000Z"
},
"event": "verified",
"application": "none",
"occurred_at": "2026-10-22T18:05:12.000Z"
},
"signature": "<64 hex>"
}
200
{
"ok": true,
"event_id": "ainevt_3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c",
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"event": "verified",
"application": "none",
"duplicate": false
}
POST/deliveries/{receipt_id}/outcomeReceiver key

Report a sale, or no sale, after a delivery you verified.

  • Send it from your order or checkout code. The receipt id arrives on the landing URL (aix_receipt), and the AIX tag keeps it in the first-party aix_receipt cookie for an hour.
  • order_value_cents is optional and counted only with "converted": true. A later report replaces the earlier one. 409 verified_delivery_required until you have verified the receipt.
  • Your dashboard shows these sales against your deliveries. Publishers see only totals, never a receipt or an order. Link a receipt to an order only to report it. Over MCP: report_outcome.
POST /deliveries/aindeliv_…/outcome
{ "converted": true, "order_value_cents": 12999 }
200
{
"ok": true,
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"converted": true,
"order_value_cents": 12999
}
POST/handoffs/{handoff_id}/redeemReceiver key

Redeem an AIX handoff id that reached your store, from the shopper's landing or an agent's checkout.

  • From a landing: your server read ?aix=<handoff_id>; send {"via": "landing"}. From an agent's checkout: UCP attribution.aix_handoff or ACP affiliate_attribution.token; no body needed.
  • Delivers the shopper's request and charges your live campaign as a click would, once. A repeat answers the same receipt free, with duplicate: true. redeemed: false carries a reason (expired after an hour, not_redeemable, delivery_failed or another click check) and charges nothing.
  • Only the store the handoff was reported for can redeem it, within one hour of the report; any other id answers 404 unknown_handoff. A sandbox key gets a free test delivery for any well-formed id, with test: true. A key that is not a receiving agent's answers 403 receiver_app_required.
POST /handoffs/aixho_…/redeem
{ "via": "landing" }
200
{
"redeemed": true,
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"context_url": "https://aix.brambles.ai/api/aix/deliveries/aindeliv_0123456789abcdef0123456789abcdef"
}
// repeat: { "redeemed": true, "receipt_id": "aindeliv_…", "context_url": "…", "duplicate": true }
// sandbox: { "redeemed": true, "test": true, "receipt_id": "aindeliv_…", "context_url": "…" }
// refused: { "redeemed": false, "reason": "expired" }
POST/landingNo key (the AIX tag)

What the AIX tag calls when a shopper lands with ?aix=: it claims the handoff for your site.

  • Add <script src="https://aix.brambles.ai/aix-tag.js" async></script> to every page instead of calling this yourself.
  • The browser's Origin must be your verified domain, and the visit passes the same checks as a click. The tag keeps the receipt id in a first-party aix_receipt cookie and fires an aix:arrival event; your server reads the request with GET /deliveries/{receipt_id}. Limited to 30 calls a minute per IP address.
POST /landing
{ "aix": "aixho_2f9c0d0123456789abcdef0123456789" }
200
{ "ok": true, "receipt_id": "aindeliv_0123456789abcdef0123456789abcdef" }
POST/test-deliveriesReceiver key (sandbox)

Send your sandbox receiving agent a free, signed test delivery.

  • Sandbox keys only, 10 a minute. Pushes to your sandbox webhook when one is set, otherwise leaves the delivery to pull. The console's "Send a test delivery" button sends the same delivery; so does sandbox redeem_handoff over MCP, or POST /handoffs/{handoff_id}/redeem with any well-formed id and a sandbox key.
  • Acknowledge it verified to flip the console to Connected and unlock the production key.
  • status is claimed when your webhook answered with a valid signed verified acknowledgment, otherwise the delivery's current state (for example needs_retry with error set). context_url is where to fetch the verified context.
201
{
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"environment": "test",
"member_id": "aixapp_49356ce196553f361f44:test",
"delivery": "webhook", // or "pull" when no sandbox webhook is set
"status": "claimed", // claimed = your webhook answered with a valid signed verified acknowledgment
"error": null,
"acknowledged": true,
"context_url": "https://aix.brambles.ai/api/aix/deliveries/aindeliv_0123456789abcdef0123456789abcdef"
}

Rate limits and caps

Limits are per principal and per minute. Over the limit the answer is 429 rate_limited, usually with retry_after in seconds; back off and carry on without AIX, never block the shopper.

  • POST /intents: 120 (30 for message-only intents, which run an extraction)
  • POST /handoffs: 120
  • POST /handoffs/{handoff_id}/redeem: 120
  • POST /privacy/delete: 120
  • GET /activity: 60
  • GET /deliveries: 60
  • POST /deliveries/ack: 120 (its 429 carries no retry_after)
  • POST /test-deliveries: 10
  • MCP (/mcp): 120
  • POST /landing: 30 per IP address

Caps and windows every sending agent should plan for:

  • A new publisher organization gets at most 100 paid deliveries a day for its first 30 days. After that the default is 1,000 paid deliveries a day; Brambles can raise it for you. Handoffs over the cap still send the shopper to the store, unbilled (publisher_cap under clicks_not_billed).
  • An AIX link (handoff_url) delivers only when the shopper opens it within one hour of the handoff report. A link opened again from a different network address sends the shopper to the store without the receipt.
  • Automated opens never deliver and never close the link: crawlers, link previews, prefetch and prerender, HTTP libraries and headless browsers all pass straight through to the store.
  • In the sandbox the AIX link shows a test page naming the destination instead of redirecting, so nothing you test ever reaches a real store.
  • Destinations must be https URLs on the store's own domain. A store can redeem a handoff id once, within the same hour.

Webhook delivery

Save a webhook URL in the console and each delivery is pushed as a JSON envelope with the header x-ain-signature: t=<unix seconds>,v1=<hex>: an HMAC-SHA256 with your receiver secret over "aix-webhook-v1\n" + t + "." + raw body; a second header, x-ain-receipt-format, names the receipt's credential type. The body has six fields: schema_version, reference, receipt, ain_member_id, delivered_at and acknowledgment_url (JSON Schema at /aix/spec/delivery.v1.schema.json). Verify the raw body before parsing it, reject timestamps older than a minute, then fetch the verified context or verify the receipt yourself against /.well-known/jwks.json. The Node and Python cards on the quickstart carry the verification code.

POST https://your-store.example/aix/webhook
{
"schema_version": "aix.delivery.v1",
"reference": {
"receipt_id": "aindeliv_0123456789abcdef0123456789abcdef",
"member_id": "aixapp_49356ce196553f361f44",
"receipt_sha256": "922a33572acd28986b32c3b2213020d40dc793e5bf83e01d1672db1ea2bce4d1",
"task_id": "task_c0ffee0123456789",
"revision": 3,
"expires_at": "2026-10-23T18:04:00.000Z"
},
"receipt": "eyJhbGciOiJFUzI1NiIsInR5cCI6InZjK3NkLWp3dCJ9…~…",
"ain_member_id": "aixapp_49356ce196553f361f44",
"delivered_at": "2026-10-22T18:04:03.000Z",
"acknowledgment_url": "https://aix.brambles.ai/api/aix/deliveries/ack"
}

MCP tools

The same operations as tools over Streamable HTTP at https://aix.brambles.ai/api/aix/mcp, with the same bearer header. Tools are scoped to the key. A publisher key exposes four tools: submit_intent, report_handoff, delete_session and get_activity. A receiver key exposes the receiver operations as tools: subscribe_intents, get_intent_context, pull_intent, claim_intent, redeem_handoff, acknowledge_delivery, delivery_status, report_outcome and get_activity. With a sandbox key, redeem_handoff returns a free test delivery for any well-formed handoff id. The MCP submit_intent tool takes { "intent": { … } }, the same body as POST /intents; its turn input is reserved for Brambles-managed publishers, and exactly one of the two must be sent. There is no test-delivery MCP tool: a sandbox receiver key gets one from redeem_handoff with any well-formed handoff id. Self-serve keys are bearer tokens, sent as they are; hosts that cannot send a custom header need an OAuth flow, which AIX does not offer to self-serve agents. The MCP cards on the quickstart have the config for each client.

mcp.json
{
"mcpServers": {
"aix": {
"type": "http",
"url": "https://aix.brambles.ai/api/aix/mcp",
"headers": { "Authorization": "Bearer ${AIX_PUBLISHER_KEY}" }
}
}
}

OpenAPI document

The HTTPS API as OpenAPI 3.1, for code generation, API clients and agents: www.brambles.ai/aix/openapi.v1.yaml. Contracts are versioned by schema id (aix.shopping-context.v1, aix.delivery.v1, aix.delivery-ack.v1); a new major version ships beside the old one. Dated changes are in the changelog.

Was this helpful?