# Serving the Agent Platform API contract With Agent Platform's bring your own container (BYOC) deployment option, you need to serve the standard API contract yourself. In [The Agent Platform API contract: what your agent must expose, and why](/agents/ap-api-contract/), I covered why the contract exists and walked through the thirteen methods that `AdkApp` exposes. Now let's put a serving layer around them and call the deployed agent end to end. On the managed paths (the pickle, stub, and source options from [the deploy post](/agents/5-ways-to-deploy/)), the platform builds the serving layer for you — `AdkApp` already has all thirteen methods, and the platform either introspects the list or you declare it. You never see the plumbing. Bring your own container, and the plumbing is yours. Agent Runtime receives the camelCase `classMethod` envelope at `:query` / `:streamQuery`, changes that field to snake_case `class_method`, and forwards the call to your container at two fixed routes: ``` POST /api/reasoning_engine {"class_method": "...", "input": {...}} (sync + async) POST /api/stream_reasoning_engine {"class_method": "...", "input": {...}} (stream + async_stream) ``` Your job is a small dispatch wrapper: look up `class_method` on the `AdkApp`, call it with `input` as kwargs, and return the result in the shapes the platform expects — `{"output": ...}` as JSON for the sync route, newline-delimited JSON events for the streaming route. Here's the heart of ours (FastAPI): ```python adk_app = agent_engines.AdkApp(agent=root_agent) @app.post("/api/reasoning_engine") async def query(http_request: Request) -> responses.JSONResponse: request = await _parse_request(http_request) method = getattr(adk_app, request.class_method) output = await _invoke_callable_or_raise(method, request.input or {}) return responses.JSONResponse( content=encoders.jsonable_encoder({"output": output}) ) @app.post("/api/stream_reasoning_engine") async def stream_query(http_request: Request) -> responses.StreamingResponse: request = await _parse_request(http_request) method = getattr(adk_app, request.class_method) output = await _invoke_callable_or_raise(method, request.input or {}) return responses.StreamingResponse( content=json_generator(output), media_type="application/json", ) ``` The full wrapper — including handling both sync and async generators in the stream — is [main.py](https://github.com/WilliamDenniss/agent-examples/blob/b79d454c65d61a049aa57cd2b149de37dfc460be/08_ContainerizedForAgentPlatform/main.py) in the repo, with the [Dockerfile](https://github.com/WilliamDenniss/agent-examples/blob/b79d454c65d61a049aa57cd2b149de37dfc460be/08_ContainerizedForAgentPlatform/Dockerfile) alongside it. A few things the platform handles for you: it injects `$PORT` (listen on `0.0.0.0` there), and it injects `GOOGLE_CLOUD_AGENT_ENGINE_ID` — the engine's own resource id — which `AdkApp.set_up()` uses to wire sessions and Memory Bank to the agent's own resource. That's how the container stays stateless while `create_session` still works. One wrinkle I hit running this for real: Agent Runtime forwards the sync `:query` body to the container as a JSON-encoded *string*, not an object. So parse defensively — decode once, and if you've still got a string, decode again (that's what `_parse_request` does above). The other half is declaring the list at deploy time, since the platform can't introspect a container. It's the same short name-plus-`api_mode` list from the table, in the create request's `spec`: ```json "classMethods": [ {"api_mode": "", "name": "create_session"}, {"api_mode": "", "name": "get_session"}, {"api_mode": "stream", "name": "stream_query"}, {"api_mode": "async_stream", "name": "async_stream_query"} ] ``` The full thirteen-entry version, plus the IAM and Artifact Registry setup around it, is in [deploy_byoc.sh](https://github.com/WilliamDenniss/agent-examples/blob/master/09_DeployContainerToAgentPlatform/deploy_byoc.sh). The rule to remember: declare only what your wrapper can dispatch — the platform routes strictly off your list, and `getattr` does the rest. *Update: Google now documents the container routes, field names, and response shapes on a single [Agent Platform runtime contract](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/runtime/runtime-contract) page. The ADK-specific method list is still tied to the `AdkApp` version you deploy; the list above is the one verified against this example.* ## Taking it for a spin The whole loop, from the caller's chair. Create a session, keep the id: ```shell SESSION_ID=$(curl -s -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ "https://${REGION}-aiplatform.googleapis.com/v1/${RESOURCE_NAME}:query" \ -d '{"classMethod": "create_session", "input": {"user_id": "user1"}}' \ | jq -r '.output.id') ``` Then run a trading cycle in that session, filtering the event stream down to the agent's text: ```shell curl -s -X POST \ -H "Authorization: Bearer $(gcloud auth print-access-token)" \ -H "Content-Type: application/json" \ "https://${REGION}-aiplatform.googleapis.com/v1/${RESOURCE_NAME}:streamQuery" \ -d '{"classMethod": "stream_query", "input": {"user_id": "user1", "session_id": "'"$SESSION_ID"'", "message": "Run the trading cycle."}}' \ | jq -rj 'select(.content.parts) | .content.parts[] | select(.text) | .text' ``` Drop the `jq` filter and you'll see the raw events instead — the news-fetch tool calls going out, the sentiment analysis coming back, each `place_trade_order` with its ticker and amount, and finally the report. The runnable version with error handling is [trade.sh](https://github.com/WilliamDenniss/agent-examples/blob/master/09_DeployContainerToAgentPlatform/trade.sh), and it works against *any* of the deployment options, not just the container — same resource, same contract. So that's what's behind those two endpoints: a session suite so conversations persist, a streaming query so users watch the agent work, and memory methods so it learns across sessions. If you're deciding how to get your own agent up there, [the deploy post](/agents/5-ways-to-deploy/) walks the five options — the contract at the end is the same either way.