axp.one

Connect your AI app

SDK guide

Ask for an ad next to your own answer, show one Sponsored card below it, and confirm it appeared.

First, choose where you are connecting

There are two separate places. Each has its own address and its own key. Use a key only with its own address.

Your accountYour own app, private to you
API address
/api/accounts/sdk
API key
You create it under Your AI apps, API keys. Shown once.
What you see
Only your app, your deliveries and your earnings.
Shared demoThe public example chat
API address
/api/product
API key
Issued by the AXP team for the shared demo. You cannot create one.
What you see
Public example campaigns that anyone can see and change.
  1. Step 1Your server

    Request

    Ask for an ad at the same time as you ask your own model for an answer.

    const adTask = axp.requestAd({
      question, sessionId, turnId,
    });
    An award, or no fill
  2. Step 2Your app

    Render

    For an award, add the approved card below your answer with its Sponsored label.

    const node = createSponsoredCard({
      document, award: ad.award,
    });
    sponsorSlot.append(node);
    A separate Sponsored card
  3. Step 3Your app to your server

    Acknowledge

    Confirm the card is on the page, then send that confirmation for a receipt.

    await acknowledge({
      node, award: ad.award,
      deliveryToken: ad.deliveryToken,
    });
    One accepted charge
Your answer stays independent.

Run your own model in parallel and show its answer as soon as it is ready. If there is no ad, or the ad request fails, the answer is unaffected.

Set up your server

Keep the key in your server’s private environment. Never put it in browser code, a prompt or a URL.

Your account
# Your own app. Copy the exact address from Your account > Your AI apps > Connect.
AXP_EXCHANGE_URL=https://axp.one/api/accounts/sdk
AXP_PUBLISHER_API_KEY=<the API key you created, shown once>
Shared demo
# The shared example chat only. Its key is issued by the AXP team.
AXP_EXCHANGE_URL=https://axp.one/api/product
AXP_PUBLISHER_API_KEY=<key provided for the shared demo>

The SDK is not published to npm yet. Copy the SDK source into your project or use it as a local package. It needs Node 22.18 or later and has no runtime dependencies. View SDK source ↗

1. Request an ad next to your answer

Send one cleaned question of up to 1,200 characters, a random session ID per conversation and a stable turn ID per message. Leave private data and chat history out.

Your server
import { AXPPublisher } from './packages/publisher-sdk/index.mjs';

const axp = new AXPPublisher({
  apiKey: process.env.AXP_PUBLISHER_API_KEY, // server only
  baseURL: process.env.AXP_EXCHANGE_URL, // your account address, or the shared demo's
  timeoutMs: 15000,
  receiptTimeoutMs: 45000,
});

// Start both before awaiting either. The answer receives no sponsor material.
const adTask = axp.requestAd({ question, sessionId, turnId,
  placementId: 'chat-sponsored-card',
  settlementNetwork: 'solana-devnet' });
const answerTask = yourExistingLLM({ question });

sendAnswer(await answerTask); // stream independently if your app streams
const ad = await adTask;
if (ad.status === 'awarded') sendSponsoredAward(ad);
// no_fill or error: keep the answer and leave the slot empty
Details: your functions, limits and timeouts

yourExistingLLM, sendAnswer and sendSponsoredAward are your app’s own functions. Your answer path receives no advertiser text, hints or buying evidence.

Session and turn IDs use letters, digits, underscores or hyphens, up to 120 characters. You can also pass required features or category exclusions.

The ad request waits up to 15 seconds and the acknowledgement up to 45 seconds; both can be changed. A timeout does not prove nothing happened on the server, and an ad request is never retried for you.

On the shared demo, set settlementNetwork to solana-devnet or tempo-testnet. Only campaigns on that network compete. Start a new session when you switch.

2. Render the card and 3. acknowledge it

Send only an awarded response and its delivery token to the browser. The helper renders plain text, an https destination and the word Sponsored in a separate card below your answer.

Your app (browser)
import { createSponsoredCard, createRenderAcknowledger }
  from './packages/publisher-sdk/browser.mjs';

// Run only for an awarded response received from your server.
const acknowledge = createRenderAcknowledger({
  post: async (_path, observation, deliveryToken) => {
    const response = await fetch('/api/axp/render', {
      method: 'POST',
      headers: { 'content-type': 'application/json', 'x-app-csrf': yourCSRF },
      body: JSON.stringify({ awardId: ad.award.id,
        observation, deliveryToken }),
    });
    if (!response.ok) throw new Error('delivery_unconfirmed');
    return response.json();
  },
});

const node = createSponsoredCard({ document, award: ad.award });
sponsorSlot.append(node);
const receipt = await acknowledge({ node, award: ad.award,
  deliveryToken: ad.deliveryToken });
Your server: forward the confirmation
// Your /api/axp/render server handler, after app authentication + CSRF:
const receipt = await axp.acknowledgeRender({
  awardId: body.awardId,
  deliveryToken: body.deliveryToken,
  observation: body.observation,
});
// receipt.status === 'accepted': inspect charge.id and receiptHash.
// Reconcile an uncertain result with this same award and delivery token.
Details: what is checked and what a receipt means

Your own /api/axp/render route signs in the browser request and forwards the confirmation with your server key. The key never reaches the browser or your model’s prompt.

Before posting, the helper checks the card’s award ID, exact text, original destination, visible Sponsored label and that it is on the page. Add your own styles through className. A React version, SponsoredCard, is in packages/publisher-sdk/react.tsx.

An award holds budget. An accepted receipt creates one charge. The browser cannot choose an amount, a recipient or a signing key.

A receipt shows that the card was added with its label. It does not show that anyone read it, clicked it or bought anything, and it is not a payout.

Readiness and troubleshooting

In your account, the Connect tab of your app checks your setup and lists anything missing. It requests no ad.

What you seeWhat it meansWhat to do
Your server gets 401The key is wrong, expired (keys last 30 days) or revoked.Create a new key under API keys and update your server.
A request from the browser is rejectedA key must never leave your server.Call AXP from your server and pass only the award to the browser.
status is no_fillNo campaign fit this question or none bid. This is normal.Show your answer and leave the slot empty.
status is error or the request times outThe ad request did not complete. Nothing is retried for you.Show your answer. If you retry, send the same question, session and turn.
The card cannot be renderedAn award is holding budget for a card that will not appear.Call failRender to release it. Otherwise it expires on its own.
The acknowledgement result is unknownThe receipt may or may not have been accepted.Acknowledge again with the same award and delivery token. It never charges twice.
Your account says "Not ready yet"Something in your setup is missing, such as an active key.Open Your AI apps, then Connect, and follow the list shown there.
Release an award you cannot render
await axp.failRender({
  awardId: ad.award.id,
  deliveryToken: ad.deliveryToken,
  reason: 'render_failed',
});

An accepted charge cannot be removed by failing its award. Acknowledging an accepted receipt again returns the same charge.

Reference

Open only what you need.

Payments and test balances

Accounts currently use test balances: accepted receipts add up, and no real money moves. Payouts from an account are not available yet.

The shared demo can run on a test network with test tokens. There, an accepted delivery advances a running voucher total. Tempo supports payouts while a channel stays open; closing returns unused funds on either network. The example chat’s Payment stage shows those states and their recorded evidence separately.

Connect to the shared demo exchange

Read the shared demo configuration for its placement and supported features. A key for the shared demo is issued by the AXP team; the public configuration does not hand out keys.

The shared demo uses Solana Devnet test USDC or Tempo Testnet PathUSD. Its example chat uses DeepSeek for the answer and Jev for buying decisions; your own app keeps its own answer model.

Run the developer example on your machine

Against the shared demo

With a shared demo key in your server environment, run this from the repository root:

AXP_EXCHANGE_URL=https://axp.one/api/product \
  node packages/publisher-sdk/examples/server.mjs
# Open http://127.0.0.1:3433.
# The ad and example-answer requests use the shared demo.

Starting it sends nothing. Sending a question uses the shared demo’s daily allowance.

Against a local exchange

npm run demo:product
# Create, approve and launch a campaign at http://127.0.0.1:3430.

AXP_EXCHANGE_URL=http://127.0.0.1:3430/api/product \
  node packages/publisher-sdk/examples/server.mjs

The launcher stores its generated server key in ignored local-state/product/publisher-api-key, and the example reads it on the server. To use a different local workspace, set the same AXP_PRODUCT_STATE_DIR in both, or pass its key as AXP_PUBLISHER_API_KEY.

Local test credits are separate from the hosted shared demo. The example chat needs a live answer model and never invents an answer.

Integration research: Gravity’s official server request and render quickstart informed this sequence. AXP uses its own exchange and receipt contracts.