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.
curl https://aix.brambles.ai/api/aix/activity?days=7 \ -H "Authorization: Bearer $AIX_PUBLISHER_KEY"/intentsPublisher keySubmit the shopper's current shopping task for one session.
session_idis your conversation id, 1 to 128 characters fromA-Z a-z 0-9 _ . : -. AIX stores only a keyed hash of it.- Send
taskwhen your agent already understands the request; otherwise sendmessagewith the shopper's words and AIX extracts the task (30 message-only calls a minute instead of 120). facts[].attributeis one of brand, width, size, department, use_case, feature, merchant, deal.strengthis required, preferred or excluded.country(ISO 3166-1) is required in production, orlocalewith a region. Never ask the shopper; read it from your host's geolocation header. A sandbox intent without one is accepted with awarning(country_required) that production would turn intosuppressed.- 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_taskorcommon_task;repeated_task,implausible_budgetandjunk_textcost 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.
{ "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 }}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 session200 { "status": "suppressed", "reason": "country_required" } // production only; the sandbox accepts with a warning200 { "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/handoffsPublisher keyReport that the assistant is linking the shopper to a store; returns what to add to your link.
destination_urlis the store's own page URL (https). When a participating store would pay (delivers_on_click), addaix_param(aix=<handoff_id>) to the store URL inside the link you already use, affiliate, ad or plain;landing_urlis 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; otherwisestatusisconsent_requiredand the shopper goes to the store unshared.not_deliveredmeans 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_failedmeans the exchange could not deliver.deliveredwithduplicate: truemeans this version of the task was already paid for at this store;handoff_urlthen carries the receipt. The sandbox answerstest_deliveredwith areceiver_preview.suppressed(reasonsensitive_categoryorprivacy_signal) shares nothing and withdraws the session;handoff_urlis the original URL. A handoff needs an accepted intent for the same session first; otherwise409 no_active_intent.- Every answer has
handoff_urlanddelivered; a recorded handoff addshandoff_id,environment(liveortest),earnings_centsandquality_score.
{ "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" }}{ "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" }/privacy/deletePublisher keyHonour 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.
{ "session_id": "conv_123" }{ "status": "deleted", "environment": "live", "intents_deleted": 2, "receipts_cleared": 1 }/activityEither keyYour 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_deliveredcounts by status (not_delivered,consent_required,delivery_failed);handoffs.clicks_not_billedcounts opened links that were not billed, by reason (expired,publisher_server,too_fast,ip_velocity,low_quality,publisher_cap).earnings.revenue_share_bpsis your share of each paid delivery. - After your first sandbox handoff or verified test delivery this is what flips the console to Connected.
{ "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 })/receiver/configReceiver keyMember id, issuer, derived receiver secret and acknowledgment URL. Server only.
- Keep
receiver_secretin your server's secret store. Never return it to a browser, a model, an MCP tool result or a skill file. - Pin
issuerfrom this response; it is not derived from the API hostname.member_idis your principal: the agent id with a production key,<agent id>:testwith a sandbox key, each with its own secret. There is no:livesuffix. Sandbox deliveries never verify with production settings. - This endpoint answers 401 for a missing key and for a key without the
claimscope.
{ "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"}/deliveriesReceiver keyPoll your recent deliveries (pull mode).
?limit=20&since=<ISO time>(limit 1 to 100), newest first. Use eachreceipt_idwithGET /deliveries/{receipt_id}.statusispending(waiting for your pull or acknowledgment),claimed(you acknowledged itverified),needs_retryorpermanently_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 purposeaix-webhook-v1).
{ "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" } ]}/deliveries/{receipt_id}Receiver keyVerified agent context and recorded events for one receipt.
- The exact delivery
reference, the readablebrief, the canonicalshopping_context(aix.shopping-context.v1), the derivedactivationplan and the acknowledgmenteventsrecorded 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_unavailablewhen the receipt is not addressed to this key, expired or unknown;400 invalid_receipt_idfor anything that is not anaindeliv_id. This endpoint answers 401 for a missing key and for a key without thepullscope.
{ "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" } ]}/deliveries/ackReceiver keySubmit 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_idis your principal (the agent id, or<agent id>:testin the sandbox); any other value answers409 ack_not_accepted. verified(applicationnone) means the receipt was verified and durably stored.appliedmeans your backend actually loaded the context into the agent turn (agent_context_loaded) or ranked its catalog (catalog_ranked).appliedrequires a priorverified.- Logical events are deduplicated, so retries are safe: a repeat answers the same
event_idwithduplicate: true.409 ack_not_acceptedon a reference mismatch, a bad signature, an expired receipt, orappliedbeforeverified. This endpoint answers 401 for a missing key and for a key without theclaimscope; its 429 carries noretry_after.
{ "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>"}{ "ok": true, "event_id": "ainevt_3b1c9d2e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c", "receipt_id": "aindeliv_0123456789abcdef0123456789abcdef", "event": "verified", "application": "none", "duplicate": false}/deliveries/{receipt_id}/outcomeReceiver keyReport 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-partyaix_receiptcookie for an hour. order_value_centsis optional and counted only with"converted": true. A later report replaces the earlier one.409 verified_delivery_requireduntil 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.
{ "converted": true, "order_value_cents": 12999 }{ "ok": true, "receipt_id": "aindeliv_0123456789abcdef0123456789abcdef", "converted": true, "order_value_cents": 12999}/handoffs/{handoff_id}/redeemReceiver keyRedeem 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: UCPattribution.aix_handoffor ACPaffiliate_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: falsecarries areason(expiredafter an hour,not_redeemable,delivery_failedor 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, withtest: true. A key that is not a receiving agent's answers403 receiver_app_required.
{ "via": "landing" }{ "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" }/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_receiptcookie and fires anaix:arrivalevent; your server reads the request withGET /deliveries/{receipt_id}. Limited to 30 calls a minute per IP address.
{ "aix": "aixho_2f9c0d0123456789abcdef0123456789" }{ "ok": true, "receipt_id": "aindeliv_0123456789abcdef0123456789abcdef" }/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_handoffover MCP, orPOST /handoffs/{handoff_id}/redeemwith any well-formed id and a sandbox key. - Acknowledge it
verifiedto flip the console to Connected and unlock the production key. statusisclaimedwhen your webhook answered with a valid signedverifiedacknowledgment, otherwise the delivery's current state (for exampleneeds_retrywitherrorset).context_urlis where to fetch the verified context.
{ "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: 120POST /handoffs/{handoff_id}/redeem: 120POST /privacy/delete: 120GET /activity: 60GET /deliveries: 60POST /deliveries/ack: 120 (its 429 carries noretry_after)POST /test-deliveries: 10MCP (/mcp): 120POST /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_capunderclicks_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
httpsURLs 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.
{ "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.
{ "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.