Expose Your AI Agent as an API (With an OpenAPI Spec You Didn't Write)
Turning an agent into an API usually means a FastAPI wrapper, duplicated request models, and a hand-maintained OpenAPI file that drifts. On a2a cloud every typed skill becomes POST /invoke/{skill} with a generated OpenAPI 3.1 contract, a typed TS client, MCP, and a signed receipt per call.
Expose Your AI Agent as an API (With an OpenAPI Spec You Didn't Write)
You built an agent. It works in a notebook, or in langgraph dev, or in a CLI loop. Now someone else needs to call it — a frontend, a cron job, a backend service, another team's agent — and "someone else needs to call it" is where most agent projects quietly turn into a web-framework project.
You wrap it in FastAPI. You write request models that duplicate the function signature. You hand-maintain an OpenAPI file that drifts from the code by Thursday. You add a bearer check. You bolt on streaming because the agent takes forty seconds. Then you do it all again for the MCP clients who want to call the same thing as a tool.
On a2a cloud, the API is not a second artifact. It's derived from the agent.
One typed method in, one HTTP endpoint out
An agent on a2a cloud is a Python class with typed @skill methods:
from pydantic import BaseModel
from a2a_pack import A2AAgent, RunContext, skill
class Triage(BaseModel):
severity: str
owner: str
summary: str
class SupportAgent(A2AAgent):
name = "support-agent"
description = "Triage inbound support tickets"
@skill
async def triage(self, ctx: RunContext, ticket: str, customer_tier: str = "free") -> Triage:
... # your LangGraph graph, CrewAI crew, or OpenAI Agents SDK run goes hereDeploy it:
pip install a2a-pack
a2a deployThat skill is now an HTTP endpoint:
curl -X POST https://support-agent.a2acloud.io/invoke/triage \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"arguments": {"ticket": "Checkout 500s since 09:14 UTC", "customer_tier": "pro"}}'JSON in, JSON out. The input schema is built from your type hints: ticket is required because it has no default, customer_tier is optional, and unknown fields are rejected (additionalProperties: false). Every call is validated with Pydantic before your handler runs — bad input is an HTTP 400, not a stack trace halfway through an LLM call.
The OpenAPI spec is generated, not maintained
Every deployed agent serves its own contract:
https://support-agent.a2acloud.io/.well-known/openapi.jsonIt's OpenAPI 3.1, one POST /invoke/{skill} operation per skill, with the request schema, the response schema, whether the skill streams, the scopes it requires, and the auth strategy (in an x-a2a-auth extension). Because it's generated from the same signatures the runtime validates against, it can't drift from the code.
Want it at build time instead — for CI, a contract review, or a client in another language?
a2a openapi spec --project . --base-url https://support-agent.a2acloud.io --out openapi.jsonAnd if the caller is a TypeScript frontend, generate a typed client straight from the skill contract:
a2a openapi client --project . --skill triage --out frontend/src/a2a-clientimport { invokeTriage } from "./a2a-client";
const { data } = await invokeTriage({
body: { arguments: { ticket: "Checkout 500s since 09:14 UTC" } },
});Rename a parameter in Python, regenerate, and the TypeScript compiler tells you every call site that broke. That's the whole point of a contract.
Same agent, every protocol
The HTTP endpoint is one of several surfaces the same deploy produces — you don't pick one:
- `/invoke/{skill}` — plain JSON-over-HTTP for backends, cron jobs, webhooks, and scripts.
- `/mcp` — the same skills as MCP tools, so Claude, Cursor, or any MCP client can call them. See wiring an MCP server to your agent.
- A2A —
/.well-known/agent-card,/message:send,/message:stream,/tasks/{id}for agent-to-agent calls. - `/app` — an optional frontend that calls the generated client.
One skill definition, four callers. When you change the signature, all of them change together.
An API for an agent needs more than routing
A normal API endpoint runs deterministic code. An agent endpoint runs something that decides, at request time, which tools to call and what data to touch. That changes what "exposing it" has to include:
- Auth that means something. By default the runtime checks a bearer token; agents can instead plug in an auth resolver (OIDC, your own API) and get the resolved caller as a typed principal on
ctx. The caller's identity is part of the run, not a header you forgot to forward. - Scoped authority, not ambient credentials. Skills declare the scopes they need, and those scopes show up in the spec. The agent acts through scoped grants rather than a god-token in its environment.
- State that outlives the request. Every agent is provisioned with its own managed Postgres as
DATABASE_URL— see give your AI agent a Postgres database. - Proof of what each call did. Every invocation emits an Ed25519-signed receipt: who called, which skill, what grants were used, which tools ran, and the outcome. When a customer asks "what did your agent do with my request at 09:14?", you answer with a verifiable record instead of a log grep — see signed receipts vs trace logs.
That fourth point is what separates "we put our agent behind an API" from "we can let other people depend on our agent's API." Once your agent is an endpoint other systems call, disputes about what it did stop being hypothetical.
Bring your framework
The skill body is just Python. Call your LangGraph graph, kick off your CrewAI crew, or run your OpenAI Agents SDK agent inside it:
- Deploy a LangGraph agent in 60 seconds
- Deploy a CrewAI agent in 60 seconds
- Deploy an OpenAI Agents SDK agent in 60 seconds
Going the other direction — you have an existing OpenAPI spec and want an agent that can call it? That's generating agents from OpenAPI specs.
Try it
pip install a2a-pack
a2a init support-agent
cd support-agent
a2a deploy
curl https://support-agent.a2acloud.io/.well-known/openapi.jsonYour agent's API contract is already there. You didn't write it.