Skip to content

Instantly share code, notes, and snippets.

@kmjones1979
Last active July 13, 2026 13:57
Show Gist options
  • Select an option

  • Save kmjones1979/bcebad62ad0162409559cca27bf9c5e1 to your computer and use it in GitHub Desktop.

Select an option

Save kmjones1979/bcebad62ad0162409559cca27bf9c5e1 to your computer and use it in GitHub Desktop.
ampersend Platform API proposal

ampersend Platform API

Status: proposal, Jul 2026

Why this exists

ampersend can deploy spending accounts for agents, but it has no concept of an end-user. If you're building a product on top of ampersend (like ampersend-simple), you want to create accounts for your users programmatically. Today that means every agent lives under your single platform key, and the user is invisible to ampersend. There is no way to:

  • Create a real user identity on ampersend for each of your end-users
  • Let a user bring their own wallet instead of using the default embedded one
  • Change an agent's spend limits after creation
  • Revoke or rotate a session key
  • Pause an agent server-side (not just proxy-side)
  • Get webhook notifications when payments happen

The Platform API adds these things. The goal is: one API call to create a user and their spending account, get back a credential, and start paying. No dashboards, no approval flows, no redirects.

How it works

The Platform API introduces a User between the platform and the agent:

Platform (sk_live_...)
  └── User (amp_user_7x...)
       ├── Agent A (0xABCD...)
       │    └── Session Key (0x...)
       └── Agent B (0xDEF0...)
            └── Session Key (0x...)

A user can own multiple agents. This matters because different tasks need different budgets and permissions. A coding agent should not share a spending account with a research agent.

The platform's existing sk_* key authenticates everything. No new auth mechanism. ampersend scopes each user and agent to the platform that created them.

Key decisions

No custody. Session keys are always generated client-side. The platform holds them, not ampersend. ampersend only sees the derived address. This keeps ampersend out of the custody business and means a compromised ampersend server cannot spend user funds.

Multiple agents per user. Users need separate agents for separate workloads. The combined create endpoint accepts an array of agents in a single call.

Three wallet types. Managed (ampersend deploys a smart account, zero config), external (user's own wallet, tracking only), and hybrid (user's wallet routed through ampersend's authorize endpoint for server-enforced policy). All three ship together because the hybrid model is how you get real enforcement on external wallets without giving up flexibility. External-only would force platforms to choose between enforcement and wallet choice, and that's a bad trade-off.

Webhooks from day one. Platforms need to react to payments, budget alerts, and pauses without polling. Simple webhook delivery: register a URL, receive signed POST requests.

Rate limiting. Per-platform limits on user and agent creation, keyed to the platform's plan. Prevents runaway provisioning and makes costs predictable.

API reference

All endpoints use Authorization: Bearer sk_*. Amounts are micro-USDC strings (1 USDC = 1,000,000). Timestamps are unix seconds.

Users

Users are the identity primitive. A user does not need to know ampersend exists.

Create a user

POST /api/v1/platform/users
{
  "external_id": "usr_abc123",       // your user ID, unique per platform
  "display_name": "Alice",           // optional
  "email": "alice@example.com",      // optional
  "metadata": { "plan": "pro" }      // optional, opaque, stored as-is
}

Response:

{
  "id": "amp_user_7x...",
  "external_id": "usr_abc123",
  "display_name": "Alice",
  "email": "alice@example.com",
  "metadata": { "plan": "pro" },
  "created_at": 1752422400
}

Idempotent on external_id. Calling twice returns the existing user.

Other user endpoints

GET    /api/v1/platform/users/:id
GET    /api/v1/platform/users?external_id=usr_abc123
PATCH  /api/v1/platform/users/:id          (update display_name, email, metadata)
GET    /api/v1/platform/users              (list, paginated)
DELETE /api/v1/platform/users/:id          (soft-delete, pauses all agents)

Agents

An agent is a spending account. Each agent gets an on-chain smart account address and a session key for signing payments.

Create an agent under a user

POST /api/v1/platform/users/:user_id/agents
{
  "name": "research-agent",
  "wallet": { "type": "managed" },
  "spend_config": {
    "monthly_limit": "20000000",
    "daily_limit": "5000000",
    "per_transaction_limit": "500000",
    "auto_topup_allowed": false
  },
  "authorized_sellers": null
}

Response:

{
  "id": "amp_agent_9z...",
  "user_id": "amp_user_7x...",
  "name": "research-agent",
  "address": "0xABCD...",
  "status": "deployed",
  "wallet_type": "managed",
  "session_key": "0x...",
  "spend_config": {
    "monthly_limit": "20000000",
    "daily_limit": "5000000",
    "per_transaction_limit": "500000",
    "auto_topup_allowed": false,
    "monthly_remaining_usdc_micro": "20000000",
    "daily_remaining_usdc_micro": "5000000"
  },
  "balance_usdc_micro": "0",
  "created_at": 1752422400
}

session_key is returned exactly once, at creation. The platform stores it. ampersend never sees the private key, only the derived address.

Other agent endpoints

GET  /api/v1/platform/users/:user_id/agents
GET  /api/v1/platform/users/:user_id/agents/:agent_id

Combined create (recommended starting point)

For the common case of "new user, give them a spending account":

POST /api/v1/platform/accounts
{
  "user": {
    "external_id": "usr_abc123",
    "display_name": "Alice",
    "email": "alice@example.com"
  },
  "agents": [
    {
      "name": "research-agent",
      "spend_config": {
        "monthly_limit": "20000000",
        "per_transaction_limit": "500000"
      }
    }
  ]
}

Response:

{
  "user": {
    "id": "amp_user_7x...",
    "external_id": "usr_abc123",
    "display_name": "Alice",
    "email": "alice@example.com",
    "created_at": 1752422400
  },
  "agents": [
    {
      "id": "amp_agent_9z...",
      "address": "0xABCD...",
      "session_key": "0x...",
      "name": "research-agent",
      "wallet_type": "managed",
      "spend_config": { ... },
      "balance_usdc_micro": "0",
      "created_at": 1752422400
    }
  ]
}

Idempotent on user.external_id. If the user already exists, their existing agents are returned alongside any new ones.

The agents array supports multiple entries. Each gets its own smart account and session key.

Spend config

Spend limits are frozen at creation today. The Platform API makes them mutable.

Platforms need this because users change their minds. A user starts cautious with $20/month, then wants $100 after they trust the agent. Telling them to create a new account is bad UX.

PATCH /api/v1/platform/agents/:agent_id/spend-config
{
  "monthly_limit": "50000000",
  "daily_limit": "10000000",
  "per_transaction_limit": "1000000",
  "auto_topup_allowed": true
}

Pass null for any field to remove that limit. Returns the updated config with current remaining budgets. Takes effect immediately.

Session key lifecycle

Platforms need to rotate keys (scheduled hygiene), revoke them (compromised agent), and freeze accounts (user request, suspicious activity).

Add a key

POST /api/v1/platform/agents/:agent_id/keys

Returns the new key address. The old key stays valid until explicitly revoked, so the platform can rotate gracefully without downtime.

Revoke a key

DELETE /api/v1/platform/agents/:agent_id/keys/:key_address

Immediate. The server stops co-signing for this key. Any in-flight payment using this key will fail.

Pause / unpause

POST /api/v1/platform/agents/:agent_id/pause
POST /api/v1/platform/agents/:agent_id/unpause

Pause freezes all keys for this agent. The authorize endpoint returns 423 for any payment attempt. This is a real server-side kill switch, not a proxy-level one. Even if the agent has the session key and talks to ampersend directly, it cannot pay.

Funding

Funding link

POST /api/v1/platform/agents/:agent_id/funding-link
{
  "amount": "10.00",
  "destination": "agent",
  "mode": "redirect"
}

mode is "redirect" (returns a URL the user visits) or "embed" (returns a Coinbase on-ramp session token and widget URL for iframe embedding).

Embed mode exists because redirecting users to ampersend's funding page breaks the illusion that your product handles everything. Some platforms want the on-ramp inside their own UI.

Direct fund

POST /api/v1/platform/agents/:agent_id/fund
{
  "amount_usdc_micro": "5000000",
  "memo": "Welcome bonus"
}

For platforms that hold USDC and want to fund users directly. Useful for onboarding credits, referral bonuses, or any case where the platform pays instead of the user.

Payments and activity

Platforms need payment data for dashboards, billing reconciliation, and audit trails. These endpoints mirror what AgentReadClient already exposes, but authenticated with the platform key so you don't need per-agent SIWE auth.

GET /api/v1/platform/agents/:agent_id/payments?preset=30d
GET /api/v1/platform/agents/:agent_id/activity?preset=30d&limit=50&page=2
GET /api/v1/platform/users/:user_id/payments     (aggregated across agents)

Webhooks

Register a URL. ampersend sends signed POST requests when things happen.

POST /api/v1/platform/webhooks
{
  "url": "https://yourapp.com/webhooks/ampersend",
  "events": [
    "payment.completed",
    "payment.denied",
    "budget.exceeded",
    "agent.paused",
    "agent.deployed",
    "funding.received"
  ],
  "secret": "whsec_..."
}

Each webhook payload includes a signature header (X-Ampersend-Signature) computed with the shared secret. The platform verifies it before processing.

GET    /api/v1/platform/webhooks           (list)
PATCH  /api/v1/platform/webhooks/:id       (update URL or events)
DELETE /api/v1/platform/webhooks/:id

Why webhooks instead of just polling: a payment can happen at any time (the agent decides when to spend). The platform needs to update its UI, send the user a notification, or log the event in real time. Polling on a timer wastes requests and adds latency.

Wallet types

managed (default)

ampersend deploys a Safe smart account and generates a session key. Payments are co-signed by ampersend's server through the CoSignerValidator module. Spend policy is enforced server-side. The user never touches crypto.

This is what ampersend does today. It stays the default because it's the simplest integration and has the strongest security guarantees.

// No wallet config needed, managed is the default
const { agents } = await platform.createAccount({
  user: { externalId: userId },
  agents: [{ name: "my-agent", spendConfig: { monthlyLimit: 20_000_000n } }],
})

external

The platform registers the user's existing wallet address (EOA or any smart wallet). ampersend tracks payments and provides advisory budget alerts but cannot enforce spend limits because it does not co-sign.

This exists because some users already have wallets. Developer-focused platforms especially will have users who want to pay from their own address. Forcing them into a managed account means they have to move funds around, which is friction.

const { agents } = await platform.createAccount({
  user: { externalId: userId },
  agents: [{
    name: "my-agent",
    wallet: { type: "external", address: "0x1234..." },
    spendConfig: { monthlyLimit: 20_000_000n },  // advisory
  }],
})

The trade-off is clear: no enforcement. The spend config is tracked and logged, and ampersend can fire budget.exceeded webhooks, but it cannot block a transaction the user signs independently.

hybrid

The user's external wallet routes payments through ampersend's authorize endpoint. ampersend checks the spend policy before co-signing. The user's wallet signs the actual transfer.

This is the answer to "I want my own wallet AND real enforcement." It works because the payment goes through ampersend's authorization layer, just like managed accounts. The difference is that the signing key belongs to the user, not to ampersend.

const { agents } = await platform.createAccount({
  user: { externalId: userId },
  agents: [{
    name: "my-agent",
    wallet: {
      type: "hybrid",
      address: "0x1234...",
      chain: "eip155:8453",
    },
    spendConfig: { monthlyLimit: 20_000_000n },  // enforced
  }],
})

Hybrid requires the platform to run a pay-proxy (like POST /api/agent/pay in ampersend-simple) or the user's wallet to support ERC-1271 verification against ampersend's CoSignerValidator. More integration work than the other two types, but it's the only way to combine wallet choice with real limits.

It also serves as a backup signing path. If a managed account's session key is compromised, the platform can revoke it and switch the agent to hybrid mode with a fresh external key, without redeploying the smart account.

Which to use

Scenario Wallet type Why
Non-technical users, zero config managed They don't know what a wallet is
Users with existing wallets, trust-based external No enforcement needed, just tracking
Users with existing wallets, real limits hybrid Enforcement without giving up wallet choice
Key compromise recovery hybrid Revoke managed key, switch to external signer

SDK client

The new AmpersendPlatformClient wraps these endpoints:

Setup

import { AmpersendPlatformClient } from "@ampersend_ai/ampersend-sdk/ampersend"
import { createAmpersendHttpClient } from "@ampersend_ai/ampersend-sdk/x402"
import { wrapFetchWithPayment } from "@x402/fetch"

const platform = new AmpersendPlatformClient({ apiKey: "sk_live_..." })

Create a user and agent (one call)

const { user, agents } = await platform.createAccount({
  user: { externalId: "usr_abc123", email: "alice@example.com" },
  agents: [{
    name: "research-agent",
    spendConfig: {
      monthlyLimit: 20_000_000n,
      perTransactionLimit: 500_000n,
    },
  }],
})

const agent = agents[0]
// agent.address    = "0xABCD..."
// agent.sessionKey = "0x..."  (store this, it's only returned once)

Make paid requests

const paidFetch = wrapFetchWithPayment(fetch,
  createAmpersendHttpClient({
    smartAccountAddress: agent.address,
    sessionKeyPrivateKey: agent.sessionKey,
  })
)

const response = await paidFetch("https://paid-api.example.com/joke")

Create multiple agents for one user

const { agents } = await platform.createAccount({
  user: { externalId: "usr_abc123" },
  agents: [
    {
      name: "research-agent",
      spendConfig: { monthlyLimit: 20_000_000n, perTransactionLimit: 500_000n },
    },
    {
      name: "coding-agent",
      spendConfig: { monthlyLimit: 50_000_000n, perTransactionLimit: 2_000_000n },
    },
  ],
})

// agents[0].sessionKey, agents[1].sessionKey ...

Update spend limits

await platform.agents.updateSpendConfig(agent.id, {
  monthlyLimit: 50_000_000n,
  dailyLimit: 10_000_000n,
  autoTopupAllowed: true,
})

Rotate a session key

const { keyAddress } = await platform.agents.addKey(agent.id)
// deploy the new key to your agent process, then revoke the old one:
await platform.agents.revokeKey(agent.id, oldKeyAddress)

Pause an agent

await platform.agents.pause(agent.id)
// all keys frozen, authorize endpoint returns 423

Register a webhook

await platform.webhooks.create({
  url: "https://yourapp.com/hooks/ampersend",
  events: ["payment.completed", "budget.exceeded"],
  secret: "whsec_...",
})

Read payments (platform-key auth, no SIWE needed)

const payments = await platform.agents.getPayments(agent.id, { preset: "30d" })
const allUserPayments = await platform.users.getPayments(user.id)

Bring your own wallet

const { agents } = await platform.createAccount({
  user: { externalId: "usr_abc123" },
  agents: [{
    name: "my-agent",
    wallet: { type: "external", address: "0x1234..." },
    spendConfig: { monthlyLimit: 20_000_000n },
  }],
})
// spend config is advisory, payments are tracked

Migration

The Platform API is additive. Existing AmpersendManagementClient and AgentReadClient usage keeps working.

Current Platform API
ManagementClient.createAgent(...) platform.createAccount(...)
AgentReadClient.getSelf() platform.agents.get(id) or unchanged
AgentReadClient.getSpendConfig() same, plus platform.agents.updateSpendConfig(...)
ApprovalClient.requestAgentApproval(...) still valid for "connect to existing ampersend user"
(no equivalent) platform.users.create(...)
(no equivalent) platform.agents.pause(...)
(no equivalent) platform.agents.addKey(...) / revokeKey(...)
(no equivalent) platform.webhooks.create(...)

Rate limiting

Every /platform/* endpoint is rate limited per platform key.

Endpoint group Default limit Why
User + agent creation 100/min Smart account deployment costs gas, prevent runaway provisioning
Reads (users, agents, payments) 1000/min Generous for dashboards and reconciliation
Writes (spend config, keys, pause) 200/min Policy changes should be deliberate, not spammed
Webhooks CRUD 10/min Rarely changes

Limits are adjustable per platform plan. 429 responses include a Retry-After header.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment