---
title: "Minds API Integration Guide for AI Agents"
description: "Agent-oriented guide to discovering, authenticating, safely calling, retrying, polling, and presenting Minds v1 API workflows."
---

# Minds API Integration Guide for AI Agents

This guide is the operating contract for an AI agent that calls the Minds v1 REST API. It complements the [OpenAPI document](/docs/api/openapi), [complete endpoint catalog](/docs/api/reference), and domain walkthroughs.

## Choose REST or MCP

<table>
<thead>
  <tr>
    <th>
      Need
    </th>
    
    <th>
      Prefer
    </th>
    
    <th>
      Why
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Generate a typed SDK, run a backend integration, or control HTTP details
    </td>
    
    <td>
      v1 REST API
    </td>
    
    <td>
      Stable resource URLs, response envelopes, streaming, and durable job endpoints
    </td>
  </tr>
  
  <tr>
    <td>
      Let ChatGPT, Claude, Cursor, or another compatible assistant select research tools
    </td>
    
    <td>
      MCP
    </td>
    
    <td>
      Protocol-native tool schemas, annotations, OAuth, widgets, and presentation contracts
    </td>
  </tr>
  
  <tr>
    <td>
      Build a long-running service that resumes after process restarts
    </td>
    
    <td>
      v1 REST API
    </td>
    
    <td>
      Persist run, Study, draft, and export IDs in your own job state
    </td>
  </tr>
  
  <tr>
    <td>
      Run research interactively from a user conversation
    </td>
    
    <td>
      MCP
    </td>
    
    <td>
      Tool descriptions encode routing, confirmation, and safe presentation behavior
    </td>
  </tr>
</tbody>
</table>

Both surfaces call the same v1 application boundaries for supported research actions. Do not combine REST and MCP in the same logical operation unless you have an explicit reason and a stable resource ID.

## Discovery checklist

Read these resources before generating code or planning calls:

1. `https://getminds.ai/_openapi.json` — live OpenAPI 3.1 document for routes with detailed operation metadata.
2. `https://getminds.ai/docs/api/reference` — complete v1 route catalog, including routes whose detailed OpenAPI schemas are still being expanded.
3. `https://getminds.ai/docs/api/errors` — error envelope, plan limits, and retry behavior.
4. `https://getminds.ai/llms.txt` and `https://getminds.ai/llms-full.txt` — agent-oriented documentation discovery and consolidated reference text.
5. `https://getminds.ai/mcp/agents` — use this instead when your host supports MCP tools.

Do not infer that a route is unavailable merely because it is not yet present in the generated OpenAPI document. The endpoint catalog is the completeness authority; the OpenAPI document is the schema authority for operations it includes.

## Authentication and secret handling

Send a personal Minds API key as a bearer token:

```http
Authorization: Bearer minds_…
```

Agent rules:

- Read the key from a secret manager or environment variable. Never place it in prompts, source code, URLs, logs, traces, or error messages.
- Use separate keys for development and production.
- Treat the value returned by `POST /api/v1/api-keys` as write-only: it is shown once.
- Do not let an autonomous agent rotate or revoke the key it is currently using.
- On `401`, stop and request credential repair. Do not repeatedly retry.
- On `403`, do not disguise an access-policy failure as “not found.” Explain that the authenticated account lacks access.

## Stable request wrapper

Centralize authentication, JSON handling, timeouts, and error parsing:

```ts
const MINDS_BASE_URL = 'https://getminds.ai/api/v1'

type MindsError = {
  statusCode?: number
  statusMessage?: string
  message?: string
  data?: unknown
}

async function mindsRequest<T>(
  path: string,
  init: RequestInit = {},
): Promise<T> {
  const response = await fetch(`${MINDS_BASE_URL}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.MINDS_API_KEY}`,
      ...(init.body ? { 'Content-Type': 'application/json' } : {}),
      ...init.headers,
    },
    signal: init.signal ?? AbortSignal.timeout(120_000),
  })

  const contentType = response.headers.get('content-type') || ''
  const payload = contentType.includes('application/json')
    ? await response.json()
    : await response.text()

  if (!response.ok) {
    const error = payload as MindsError
    throw new Error(
      `Minds ${response.status}: ${error.message || error.statusMessage || response.statusText}`,
    )
  }

  return payload as T
}
```

Use the apex form (`https://getminds.ai/api/v1`) if your infrastructure does not follow same-origin `307` redirects for API-subdomain `GET` requests.

## Plan before mutating

Classify each proposed call:

<table>
<thead>
  <tr>
    <th>
      Class
    </th>
    
    <th>
      Examples
    </th>
    
    <th>
      Agent behavior
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Read-only
    </td>
    
    <td>
      List/read resources, status, analytics, methods
    </td>
    
    <td>
      May call when needed to answer the request
    </td>
  </tr>
  
  <tr>
    <td>
      Reversible creation/update
    </td>
    
    <td>
      Create a private draft, update a name
    </td>
    
    <td>
      State the intended target; preserve returned IDs
    </td>
  </tr>
  
  <tr>
    <td>
      Costly execution
    </td>
    
    <td>
      Ask an Audience, run a Study, retrain, regenerate
    </td>
    
    <td>
      Ensure it matches the user's request and avoid duplicate calls
    </td>
  </tr>
  
  <tr>
    <td>
      Public exposure
    </td>
    
    <td>
      Enable link sharing
    </td>
    
    <td>
      Require explicit intent; explain what becomes public
    </td>
  </tr>
  
  <tr>
    <td>
      Destructive
    </td>
    
    <td>
      Delete Minds, Audiences, Studies, chats, Formations, drafts
    </td>
    
    <td>
      Obtain explicit confirmation of exact IDs immediately before the call
    </td>
  </tr>
  
  <tr>
    <td>
      Credential control
    </td>
    
    <td>
      Mint/revoke API keys
    </td>
    
    <td>
      Keep outside autonomous research flows
    </td>
  </tr>
</tbody>
</table>

Never enable `isLinkSharingEnabled` merely to make a result convenient to access. Private is the default.

## Use IDs as durable state

Names are suitable for user interaction, but IDs are the integration contract. Persist:

- `sparkId` after Mind creation;
- `audienceId` after Audience creation;
- `studyId` after Study creation;
- `draftPlanId` and `revision` during research planning;
- `studyId` or `runId` during execution;
- `jobId` for exports;
- `itemId` for knowledge ingestion;
- `draftId` and `expectedRevision` for sidebar Study drafts.

Do not re-list by fuzzy name after a successful create response. Doing so can select the wrong resource when names are duplicated.

## Asynchronous operations

Several operations return before work is complete.

<table>
<thead>
  <tr>
    <th>
      Start operation
    </th>
    
    <th>
      Persist
    </th>
    
    <th>
      Poll/read
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Create Mind
    </td>
    
    <td>
      <code>
        sparkId
      </code>
    </td>
    
    <td>
      <code>
        GET /minds/{mindId}/training
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Create grounded Audience
    </td>
    
    <td>
      <code>
        audienceId
      </code>
    </td>
    
    <td>
      <code>
        GET /audiences/{id}/progress
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Add file/link knowledge
    </td>
    
    <td>
      <code>
        itemId
      </code>
    </td>
    
    <td>
      <code>
        GET /minds/{mindId}/knowledge/{itemId}/status
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Ask a Study in queued mode
    </td>
    
    <td>
      <code>
        runId
      </code>
    </td>
    
    <td>
      <code>
        GET /runs/{runId}
      </code>
      
       and <code>
        /events
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Run confirmed multi-question block
    </td>
    
    <td>
      <code>
        runId
      </code>
    </td>
    
    <td>
      <code>
        GET /studies/{studyId}/research-runs/{runId}
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Export
    </td>
    
    <td>
      <code>
        jobId
      </code>
    </td>
    
    <td>
      entity-specific <code>
        export-status
      </code>
      
      , then <code>
        export-download
      </code>
    </td>
  </tr>
</tbody>
</table>

Recommended polling behavior:

1. Honor `Retry-After` when returned.
2. Otherwise start at 1–2 seconds and exponentially back off to 10–15 seconds.
3. Add jitter when many jobs run concurrently.
4. Persist the last event cursor for `/runs/{runId}/events`.
5. Stop on terminal success, failure, cancellation, or `plan_limited`.
6. Set a workflow-level deadline; a client timeout does not prove the server operation failed.

## Idempotency and retries

- Retry `GET` requests after transient `429`, `502`, `503`, or `504` responses using bounded exponential backoff.
- Retry a mutation only when the endpoint documents idempotency or you supplied a stable idempotency key.
- `create_audience_from_brief` derives an idempotency key from its arguments. If the initial call times out before returning an ID, retry its exact arguments to recover the owned operation and current review or accepted Audience. Once an operation ID is available, resume that operation instead of submitting the brief again. A review is not creation or consent: confirm the unchanged accepted draft or grounding snapshot and checksum before materialization.
- `run_study_questions` supports an explicit stable idempotency key through MCP; REST callers should preserve the confirmed draft ID/revision and returned Study ID.
- Optimistic-concurrency failures on Study drafts require a fresh read and human/agent reconciliation. Do not overwrite with a guessed revision.
- Never retry destructive calls merely because the client did not receive the response. Read the resource first to determine whether deletion already succeeded.

## Direct questions versus guided Studies

Use this routing table:

<table>
<thead>
  <tr>
    <th>
      User request
    </th>
    
    <th>
      Route
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      One direct question to an existing Study
    </td>
    
    <td>
      <code>
        POST /studies/{studyId}/ask
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Knowledge-only durable direct run
    </td>
    
    <td>
      <code>
        POST /studies/{studyId}/runs
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Multiple questions, a broad objective, an asset audit, structured evidence outputs, or an explicit method
    </td>
    
    <td>
      Plan → confirm → Study lifecycle
    </td>
  </tr>
  
  <tr>
    <td>
      Export existing evidence
    </td>
    
    <td>
      <code>
        POST /studies/{studyId}/export
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Show existing evidence differently
    </td>
    
    <td>
      Read status/summary/analytics; do not recruit a new run
    </td>
  </tr>
</tbody>
</table>

The guided lifecycle is:

```text
POST research-plans/preview
  -> present the exact draft and every confirmation question
  -> revise the same draft if the user changes or answers anything
  -> receive explicit confirmation of the exact revision
  -> POST studies with that draft ID and revision
  -> poll GET studies/{studyId}
  -> GET or POST summary
```

Planning is non-executing. Never claim that research has started after a preview call.

## Method availability

Call `GET /api/v1/research-methods` before promising a named method. Treat fields as follows:

- `preparesQuestionsAtExecution: true` — the method generates deterministic questions and response contracts from its configuration at execution; saved template questions are not the full executed instrument.
- `executable: true` — the registered server adapter can run the method.
- `executable: false` — the method may be represented or planned but is not an execution promise.
- Advanced methods require explicit opt-in at execution time.
- Preserve deterministic calculations returned by the Study endpoint; do not recompute them with an LLM.

## File and URL inputs

- Upload interactive browser files with multipart form data.
- For autonomous agents, prefer public, short-lived signed, or Minds workspace-upload URLs.
- Do not embed large binary files as base64 JSON.
- Give every file a meaningful `name` and MIME `type` when known.
- URLs may be rejected by SSRF and redirect guards even when they are syntactically valid.
- Knowledge retrieval is bounded to 50 MB and a server timeout; split larger sources before ingestion.
- For respondent datasets, preview segmentation before creating a representative cohort.

## Error decisions

<table>
<thead>
  <tr>
    <th>
      Signal
    </th>
    
    <th>
      Meaning
    </th>
    
    <th>
      Agent response
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        400
      </code>
      
       / validation details
    </td>
    
    <td>
      Request does not satisfy the schema
    </td>
    
    <td>
      Correct the payload; do not retry unchanged
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401
      </code>
    </td>
    
    <td>
      Missing/invalid/expired credential
    </td>
    
    <td>
      Stop and repair authentication
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      Authenticated but not allowed
    </td>
    
    <td>
      Explain access/ownership/plan boundary
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      Resource is absent or not visible
    </td>
    
    <td>
      Verify the persisted ID and account context
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        409
      </code>
    </td>
    
    <td>
      Revision/idempotency/state conflict
    </td>
    
    <td>
      Read current state and reconcile
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        415
      </code>
    </td>
    
    <td>
      Unsupported request media type
    </td>
    
    <td>
      Correct <code>
        Content-Type
      </code>
      
       or file form
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        429
      </code>
    </td>
    
    <td>
      Rate or usage limit
    </td>
    
    <td>
      Honor <code>
        Retry-After
      </code>
      
      ; distinguish rate limiting from <code>
        plan_limited
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        plan_limited
      </code>
      
       before execution
    </td>
    
    <td>
      Nothing started
    </td>
    
    <td>
      Explain the required plan/allowance change
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status: plan_limited
      </code>
      
       during a Study
    </td>
    
    <td>
      Partial artifacts were preserved
    </td>
    
    <td>
      Report completed versus remaining questions; never call it complete
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        5xx
      </code>
    </td>
    
    <td>
      Transient or server failure
    </td>
    
    <td>
      Bounded retry for safe/idempotent calls, otherwise read state first
    </td>
  </tr>
</tbody>
</table>

## Result presentation

- Preserve citations, source URLs, shared links, download links, and resource IDs exactly.
- Clearly label synthetic responses and distinguish them from primary human research.
- Report status as queued/running/partial/completed/failed based on returned fields, not elapsed time.
- For categorical or multiselect results, preserve the server's aggregation semantics instead of forcing percentages to sum to 100 when multiple selections are allowed.
- Do not invent missing Mind answers or fill incomplete Study artifacts.
- Use the authenticated workspace link for the owner and a returned shared link only for external handoff.

## Minimal autonomous Study example

```ts
const studies = await mindsRequest<{ data: Array<{ id: string; name: string }> }>(
  '/studies',
)

const study = studies.data.find(item => item.name === 'Launch research')
if (!study) throw new Error('Study not found')

const started = await mindsRequest<{ data: { runId: string } }>(
  `/studies/${study.id}/runs`,
  {
    method: 'POST',
    body: JSON.stringify({
      question: 'What are the strongest objections to this positioning?',
      sourcePolicy: 'knowledge_only',
    }),
  },
)

// Persist started.data.runId before polling.
```

## Preflight checklist

Before a mutation, verify:

- the authenticated workspace and plan context;
- exact resource IDs and ownership/access;
- whether the request is direct research or requires a confirmed plan;
- whether sharing remains private;
- whether a previous timed-out call may already have succeeded;
- whether the operation consumes an allowance or triggers generation;
- whether explicit confirmation is required.

After a mutation, persist returned IDs, poll the documented status endpoint, and present only terminal or explicitly partial results.

## n8n workflows

For an n8n AI Agent, attach the [Minds node as a tool](/guide/integration-n8n) and select a bounded Study operation. Store authentication in a Minds API credential, keep link sharing disabled unless publication is intended, and require separate review in Minds before research starts.

## Purchase orchestration

This HTTP workflow does not imply checkout support inside every assistant. ChatGPT’s standard app rules currently exclude digital subscription and credit purchases; existing paid customers may use the research app. Do not advertise these billing tools or initiate a purchase through the ChatGPT app without explicit platform enablement. Other clients must permit the workflow and obtain buyer approval. See the [OpenAI commerce rules](https://developers.openai.com/plugins/plugin-guidelines).

Treat billing as a separate buyer-approved workflow. Begin with `GET /api/v1/billing/catalog`, choose an eligible subscription or response-credit pack, and save the buyer's decision before sending a checkout request. An existing subscriber who wants to change their plan should use Minds billing settings.

A response-credit request is small:

```json
{ "kind": "response_credits", "priceId": "price_selected_by_buyer" }
```

Submit it to `POST /api/v1/billing/checkout` with one durable purchase identifier in `Idempotency-Key`. For subscription setup, collect `legalAcceptance.termsAccepted` and `legalAcceptance.withdrawalConsent` as explicit true values; the [billing overview](/docs/api/overview) shows that request shape. Keep the purchase record together with the returned `data.checkoutSessionId` and `data.url`, and show the hosted URL without rewriting it.

Distinguish these outcomes in your workflow:

<table>
<thead>
  <tr>
    <th>
      Observation
    </th>
    
    <th>
      Agent action
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Checkout URL returned
    </td>
    
    <td>
      Ask the buyer to continue on Stripe; payment is still pending
    </td>
  </tr>
  
  <tr>
    <td>
      Connection lost before acknowledgement
    </td>
    
    <td>
      Resubmit the identical purchase with its saved key
    </td>
  </tr>
  
  <tr>
    <td>
      409 conflict or reconciliation required
    </td>
    
    <td>
      Inspect the existing purchase; do not replace its key to force another session
    </td>
  </tr>
  
  <tr>
    <td>
      Signed Stripe confirmation received
    </td>
    
    <td>
      Let the existing webhook update entitlements or response credits
    </td>
  </tr>
</tbody>
</table>

### Handling a payable invoice

Use the HTTP payment client for `POST /api/v1/billing/invoices/{invoiceId}/pay`. The server verifies the buyer's ownership before returning a quote. Eligible invoices have `collection_method: "send_invoice"`, remain open, use USD/EUR and owe at least 50 minor units. A subscription collected automatically is outside this direct-payment workflow.

Read the 402 response rather than inferring the payable total from a catalog price. MPP uses a Stripe shared payment token and needs the merchant's Business Network profile. Optional x402 settlement uses Base USDC for USD invoices, a configured facilitator and mppx route-bound credentials. Before signing, check that the challenge identifies the intended invoice and amount.

Keep wallet material out of the conversation and MCP input. With ordinary Minds bearer authentication, supply MPP in `Payment-Authorization` or x402 in `PAYMENT-SIGNATURE`. If your MPP library owns `Authorization: Payment`, put the Minds identity in `Minds-Authorization: Bearer` instead. This alternative is limited to invoice payment. Preserve the original payment credential after an uncertain response and stop on a reconciliation conflict.

### Connecting an agent payment client

Minds MCP provides research and permitted purchase-orchestration tools. The buyer's payment client supplies payment authorization; connecting Minds MCP alone does not give an assistant a wallet or permission to spend. Connect both services to a client that permits the purchase workflow. Stripe's [Link CLI](https://github.com/stripe/link-cli) can run as a separate MCP server and issue one-time shared payment tokens for MPP. The [Stripe MPP guide](https://docs.stripe.com/payments/machine/mpp) documents sandbox tokens and the payment-client flow.

The buyer signs in to their own Link account and authorizes the connection. Check buyer eligibility before promising this path: the current Link CLI documentation limits wallet access to US and Canadian Link accounts. Merchant account eligibility is a separate check; owning a US Stripe business account does not make a German buyer eligible. Check the provider's current documentation when onboarding.

Request approval for the actual invoice amount and intended merchant, then let the client submit the one-time credential directly to the HTTP payment route. Link normally asks for purchase approval. Unattended spending requires a supported, explicitly configured authorization policy with an appropriate spending limit; do not infer such a policy from a connected wallet or a conversational instruction. Additional authentication can still be required. Payment credentials stay outside model-visible tool arguments and transcripts.

For a client whose MPP library uses `Authorization: Payment`, preserve the Minds identity through `Minds-Authorization: Bearer`. A client that cannot send this alternate header needs an adapter that keeps ordinary Minds bearer authentication and sends the payment credential in `Payment-Authorization`. Never replace the buyer's Minds identity with wallet identity.

For new Pay as you go workspaces, provide `billingCountry` when reading the catalog and starting response-credit checkout. Read the catalog again if the country changes; this selects the correct Stripe entity and currency. Response packs share one unit rate and fund the shared workspace response pool.

### Example purchase journeys

For “buy 500 responses, then research our landing page,” read the catalog with the buyer’s `billingCountry` and obtain approval for the selected pack and checkout amount. Start hosted checkout with `kind: "response_credits"`, the selected `priceId`, the same `billingCountry` and a durable idempotency key. Return the Stripe URL verbatim to the buyer. After Stripe confirms payment and signed fulfillment, verify the shared response balance before starting the separately approved research workflow. New Starter subscriptions and trials are retired; existing subscriptions remain manageable in Minds billing settings.

For an existing eligible response-credit invoice, send an authenticated `POST /api/v1/billing/invoices/{invoiceId}/pay` without a payment credential. Read the returned 402 quote, obtain the buyer-authorized MPP credential, and retry the same route with that credential. Require the successful payment response and receipt, and verify the resulting credit entitlement after signed Stripe webhook processing before resuming research. Do not substitute a catalog price for the invoice total or assume this route purchases arbitrary new subscriptions in one call.

### Verification status

As of 3 October 2026, production payment discovery and an owned EUR-invoice MPP 402 challenge have been verified. A successful production MPP card purchase, receipt and entitlement activation have not yet been verified end to end. x402 settlement succeeded with test USDC in the sandbox, but Stripe's crypto PaymentIntent-recording preview activation remains pending; no successful production x402 purchase is claimed. Treat a valid discovery document or 402 challenge as negotiation, not proof of payment readiness or fulfillment. Revalidate the complete sandbox journey and an explicitly approved live purchase before advertising unattended purchasing.

### Hosted ACP continuation

For a client that speaks ACP, bootstrap from `/.well-known/acp.json` and follow its REST base URL. Version `2026-04-17` accepts a single current Stripe price, currency and capabilities object. Subscription approval belongs in metadata; seat quantity applies to Team prices. Every POST carries a purchase idempotency key.

Your state machine should wait while `continue_url` is present. Empty update/complete requests only recheck Stripe state; they cannot submit payment details or accept terms for the buyer. Cancellation expires an open owned session. See the [route reference](/docs/api/reference) for action paths and `/openapi.json` for payment-discovery metadata.

### Hosted UCP continuation

Read `/.well-known/ucp` for the REST endpoint and supported checkout version `2026-08-25`. Your platform must publish a compatible UCP profile and authenticate with a Minds OAuth metadata-document client whose HTTPS origin matches that profile. UCP operations require `UCP-Agent: profile="https://your-platform.example/.well-known/ucp"`; every write also requires a stable `Idempotency-Key`. This binding prevents an authenticated account from impersonating a different platform.

Create a checkout with `POST /api/ucp/checkout-sessions`:

```json
{
  "line_items": [{ "item": { "id": "price_selected_by_buyer" }, "quantity": 1 }],
  "context": {
    "address_country": "DE",
    "minds": {
      "legalAcceptance": { "termsAccepted": true, "withdrawalConsent": true }
    }
  }
}
```

Use a current price from the authenticated billing catalog. The selected price determines currency; optional `context.currency` must match it. Quantity greater than one applies only to Team seats, and subscriptions require the explicit legal acknowledgements above. `context.address_country` is a provisional billing-country signal; stored account ownership and verified checkout details take precedence. The server owns prices and totals; requests must not supply an amount or payment instrument.

`GET /checkout-sessions/{id}` reads the authenticated owner’s current session. Return `continue_url` verbatim to the buyer while status is `requires_escalation`. `PUT /checkout-sessions/{id}` accepts the same line item and quantity as a refresh; requested cart or context changes return a recoverable message and leave the purchase intact. To change the purchase, cancel the old open checkout and create a new one with a new key. `POST /checkout-sessions/{id}/complete` accepts `{ "payment": {} }` and rechecks Stripe’s payment state; `POST /checkout-sessions/{id}/cancel` expires an owned open session. Paths are relative to `/api/ucp`.

The profile advertises no UCP payment handlers because Stripe hosts buyer payment. A completed response includes an authenticated checkout permalink; signed Stripe webhooks still determine entitlement and credit fulfillment. An agent must not treat a continuation URL as proof of payment.

## Budget-driven response purchases

For a request such as “spend up to €200 on research responses,” explain the quote and obtain approval before creating checkout. Read the buyer’s currency and custom source offer from the billing catalog, then submit `amountMinor: 20000` with that source `priceId` and the billing country. At the current EUR rate this corresponds to 1,680 responses and a €199.92 charge.

Save the approved input alongside the purchase key in the private checkpoint. If a network timeout occurs, send the identical request again; a changed budget is a different purchase. Return the hosted URL for the buyer to finish payment, then verify the credited response pool before resuming research. This workflow uses response credits, with no separate cash wallet.

Pass `includeInvoices: true` to `get_billing_catalog`, or `includeInvoices=true` to `GET /api/v1/billing/catalog`, to include recent invoices under `subscription.invoices`. Only the billing owner or an authorized administrator can read that customer’s invoices; the default catalog omits invoice retrieval.
