ACPpotamia
User guide

Using ACPpotamia.

ACPpotamia sits between your agents and the model provider. You keep your SDK and your provider account; you change the base URL and add two headers.

Quickstart

  1. Create an account and a key on the API keys page. Keys start with acp_.
  2. Point your SDK at https://acppotamia.examopotamia.com and send your provider key in X-Upstream-Key.
  3. Name each agent with X-ControlPlane-Agent-Id. Calls then show up in the console within a few seconds.

OpenAI SDK (TypeScript)

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://acppotamia.examopotamia.com/v1",
  apiKey: process.env.ACP_KEY,
  defaultHeaders: {
    "X-Upstream-Key": process.env.OPENAI_API_KEY,
    "X-ControlPlane-Agent-Id": "support-bot",
  },
});

const reply = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{ role: "user", content: "Hello" }],
});

OpenAI SDK (Python)

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://acppotamia.examopotamia.com/v1",
    api_key=os.environ["ACP_KEY"],
    default_headers={
        "X-Upstream-Key": os.environ["OPENAI_API_KEY"],
        "X-ControlPlane-Agent-Id": "support-bot",
    },
)

Anthropic SDK (TypeScript)

The Anthropic SDK sends your Anthropic key as x-api-key, which ACPpotamia passes through. Put the ACPpotamia key in X-ACP-Key.

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({
  baseURL: "https://acppotamia.examopotamia.com",
  apiKey: process.env.ANTHROPIC_API_KEY,
  defaultHeaders: {
    "X-ACP-Key": process.env.ACP_KEY,
    "X-ControlPlane-Agent-Id": "research-agent",
  },
});

const message = await client.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 4096,
  messages: [{ role: "user", content: "Hello" }],
});

Anthropic SDK (Python)

import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://acppotamia.examopotamia.com",
    api_key=os.environ["ANTHROPIC_API_KEY"],
    default_headers={
        "X-ACP-Key": os.environ["ACP_KEY"],
        "X-ControlPlane-Agent-Id": "research-agent",
    },
)

Streaming works unchanged on both APIs. ACPpotamia proxies POST /v1/chat/completions and POST /v1/messages.

Request headers

HeaderMeaning
Authorization: Bearer acp_… or X-ACP-KeyYour ACPpotamia key. Required.
X-Upstream-KeyYour provider key. Passed through and never stored. Anthropic calls may use x-api-key instead.
X-ControlPlane-Agent-IdAgent name for the console and audit log. Default default.
X-ControlPlane-Session-IdScope for loop stop and the kill switch, such as one ticket or one job. Defaults to the agent id.
X-ControlPlane-FailModeopen or closed. See fail mode.
X-ControlPlane-Upstream: customSend the call to your team's custom endpoint.

Ids may use letters, digits and . _ : @ -, up to 128 characters.

Response headers

HeaderMeaning
X-ControlPlane-Decisionallow, block, fail-open or fail-closed.
X-ControlPlane-Block-ReasonWhy a call was blocked. See errors.
X-ControlPlane-Budget-Remaining-USDBudget left after this call's reservation.
X-ControlPlane-Overhead-MsTime ACPpotamia spent before sending the call on.
X-ControlPlane-Request-IdThe id of this call in the audit log.

Errors

Errors use one JSON shape that both the OpenAI and Anthropic SDKs understand: {"type":"error","error":{"type":"…","code":"…","message":"…"}}. Provider errors are passed through unchanged.

StatusCodeWhat to do
401missing_api_key, invalid_api_key, expired_api_keyCheck the acp_ key, or create a new one.
401missing_upstream_keySend your provider key in X-Upstream-Key.
402budget_exhausted, token_limit_reachedRaise the limit on the Limits page, or wait for the 1st of the month (UTC).
429rate_limitedWait for the number of seconds in Retry-After.
429loop_detected, session_killed, team_killedFix the loop, then revive the session or team. Retrying does not help.
403custom_upstream_unavailable, admin_scope_requiredYour plan or key does not include this.
413request_too_largeRequests can be up to 20 MB.
502upstream_unreachableThe provider could not be reached. Nothing was charged. Retry.
503control_plane_unavailableLimits could not be checked and fail mode is closed. Retry after Retry-After.

Budgets

Before a call is sent, ACPpotamia reserves its estimated cost from the team's monthly budget: the input, plus the output allowed by max_tokens (or the "output reserved per call" setting when the call sets none). When the call finishes, the reservation is replaced by the usage the provider reported.

Input tokens are estimated before the call (about four characters per token). A team can therefore go slightly over its budget, by the gap between estimate and real count of calls already running when the budget runs out. Costs use list prices per model. Models we do not recognise are charged at $10 in and $50 out per million tokens, so an unknown model never drains a budget unseen.

Loop stop

Each call's last three messages are compared with the session's recent calls. Timestamps, numbers and ids are ignored in the comparison. If a session sends the same request, or one at least as similar as your threshold, the set number of times within the time window, the session is stopped and further calls get 429 loop_detected.

Defaults: 5 matching requests within 60 seconds, similarity 0.92. Change them on the Limits page. Use a separate X-ControlPlane-Session-Id per task so one task's loop stops only that task.

Kill switch

Stop one session from the Agents page, or every agent on the team from the Overview page. Calls already streaming finish; new calls get a 429 until you revive. Reviving a session clears its loop history.

Fail mode

If ACPpotamia cannot check your limits within 1.5 seconds, fail mode decides what happens. Closed blocks the call with a 503. Open sends it unchecked and records it as "sent unchecked" in the audit log. Free plans always use closed. A caller can always ask for closed; asking for open works only when your team allows it on the Limits page. Your key is always checked, whatever the fail mode.

Custom endpoints

Pro and Enterprise teams can add one HTTPS base URL on the Limits page, for example a self-hosted model server that speaks the OpenAI or Anthropic API. Send X-ControlPlane-Upstream: custom to use it. The address comes only from your team settings, never from the request.

Admin API

Keys with admin access can manage agents without the console.

CallDoes
GET /v1/controlplane/stateSpend, held reservations, stopped sessions.
POST /v1/controlplane/killBody {"sessionId":"…","reason":"…"}. Without sessionId, stops the whole team.
POST /v1/controlplane/reviveBody {"sessionId":"…"}, or {} for the whole team.
GET /v1/controlplane/policy/exportEnterprise: your effective limits as JSON.

Webhooks

Enterprise teams can receive a POST when a session or the team is stopped. Events are session.killed and team.killed. Each delivery carries x-acppotamia-signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of t + "." + body with your signing secret. Reject deliveries older than five minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(body: string, header: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

Audit logs

Every call, blocked ones included, is recorded with time, agent, session, model, tokens, cost, outcome and overhead. Records appear in the console within a few seconds and are kept for 365 days. Pro and Enterprise teams can also keep the first 64 KB of each request and response, turned on from the Limits page. To receive your raw daily log files, write to acp@examopotamia.com.

Plan limits

FreeProEnterprise
Requests a minute2060010,000
Monthly budget ceiling$10$50,000None
Requests remembered for loop stop1664256
Request size20 MB20 MB20 MB

Getting help

Write to acp@examopotamia.com with the X-ControlPlane-Request-Id of the call. Check service status first if many calls fail at once.