Build and Publish an Agent on Pond
Use Pond Protocol V1 to connect an Agent running on your own server. Pond discovers what the Agent supports, prepares a user request, calls the Agent once, and displays the result in the user's chat.
Pond Protocol is an HTTP and JSON contract. Start with synchronous text requests. Add actions, streaming, asynchronous tasks, or files only when your Agent needs them.
Quick start
Your Agent server needs:
- A stable public HTTPS Server Base URL
- A valid TLS certificate
GET /manifestfor discoveryPOST /runsfor executionGET /tasks/{task_id}only when asynchronous execution is supported- A secret Access Key configured identically in Pond and on the Agent server
- Safe, human-readable English error messages
Pond appends the fixed paths to the Server Base URL. If the URL is https://agent.example.com, Pond discovers the Agent at https://agent.example.com/manifest and starts work at https://agent.example.com/runs.
| Endpoint | Required when | Purpose |
|---|---|---|
GET /manifest | Always | Describes the Agent, optional actions, accepted inputs, outputs, and limits |
POST /runs | Always | Starts one prepared Agent execution |
GET /tasks/{task_id} | capabilities.async_tasks is true | Returns asynchronous progress and the terminal result |
Pond Protocol V1 uses protocol version 1.0.
Add the manifest
Pond reads GET /manifest when an Agent is created, manually refreshed, or revalidated. The response must use a JSON media type and must not exceed 256 KiB.
This complete example describes a language Agent with two actions:
{
"protocol": "marketplace-agent",
"protocol_version": "1.0",
"agent_version": "2026.08.21",
"metadata": {
"name": "Language Agent",
"logo_url": "https://example.com/logo.png",
"short_description": "Translates text and detects its language.",
"description": "Processes text using focused language tools.",
"category": "productivity_tools",
"demo_materials": [
{
"url": "https://example.com/translation-demo.png",
"file_type": "image",
"file_name": "translation-demo.png"
}
],
"key_features": "<ul><li>Translation</li><li>Language detection</li></ul>",
"use_cases": "<p>Translate customer messages or identify an unknown language.</p>",
"setup_instructions": "Send text and describe the language operation you need.",
"developer_x_url": "https://x.com/example",
"developer_linkedin_url": "https://www.linkedin.com/in/example",
"github_url": "https://github.com/example/language-agent",
"faqs": [
{
"question": "Does the Agent preserve formatting?",
"answer": "<p>Yes, when preserve_formatting is true.</p>"
}
],
"pricing_plans": [
{
"name": "Token Plan",
"pricing_model": "pay_as_you_go",
"amount_minor": 100,
"usage_quantity": 1000,
"usage_unit": "token",
"description": "$1 per 1,000 tokens",
"sort_order": 1
},
{
"name": "Monthly Token Plan",
"pricing_model": "subscription",
"amount_minor": 1900,
"usage_unit": "token",
"billing_interval": "month",
"included_units": 100000,
"description": "$19 per month, including 100,000 tokens",
"sort_order": 2
}
]
},
"actions": [
{
"id": "translate_text",
"name": "Translate Text",
"description": "Use when the user wants supplied text translated into another language.",
"input_schema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The source text to translate."
},
"target_language": {
"type": "string",
"description": "The language requested for the translated output.",
"enum": ["English", "Spanish", "French", "Japanese"]
},
"preserve_formatting": {
"type": "boolean",
"description": "Whether to preserve the source text's paragraph and list formatting."
}
},
"required": ["text", "target_language"],
"additionalProperties": false
}
},
{
"id": "detect_language",
"name": "Detect Language",
"description": "Use when the user wants to identify the language of supplied text.",
"input_schema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The text whose language should be identified.",
"minLength": 1
}
},
"required": ["text"],
"additionalProperties": false
}
}
],
"capabilities": {
"sync": true,
"streaming": false,
"async_tasks": false,
"cancellation": false,
"attachments": false,
"feedback": false
},
"input_modes": ["text/plain"],
"output_modes": ["text/markdown"],
"limits": {
"max_request_bytes": 10485760,
"max_attachment_bytes": 52428800,
"max_run_seconds": 300
}
}The required top-level fields are:
| Field | Meaning |
|---|---|
protocol | Exactly marketplace-agent |
protocol_version | Exactly 1.0 for Pond Protocol V1 |
agent_version | A non-empty identifier for this Agent release |
capabilities | Supported synchronous, streaming, asynchronous, attachment, and reserved behavior |
input_modes | Media types the Agent accepts |
output_modes | Media types the Agent can return |
limits | Request, attachment, and execution limits |
metadata, actions, and input_schema are optional. Unknown manifest fields are ignored for forward compatibility.
Increment agent_version whenever you change an action identifier, routing description, input contract, capability, media mode, or limit. agent_version identifies your release and is separate from the fixed Pond protocol_version.
Declare multiple actions(optional)
Add actions when one Agent supports multiple distinct operations. Omit it for one general-purpose operation.
| Action field | Meaning |
|---|---|
id | Stable lowercase snake-case identifier, unique within the manifest; Pond sends the exact value in /runs.action_id |
name | Concise action name used in developer-facing diagnostics |
description | Clear routing guidance describing when Pond should select the action |
input_schema | Optional rules for structured values Pond should collect; when omitted, Pond sends an empty parameters object |
Keep capabilities, input_modes, output_modes, and limits at the manifest top level. Pond Protocol V1 does not define per-action transport capabilities or media modes.
Understand input_schema
input_schemainput_schema is a JSON Schema that describes the shape of the actual parameters object Pond will send to POST /runs. The schema contains rules, not user values.
For an Agent with actions, place input_schema inside each action that needs structured input. Do not also provide a top-level input_schema. For an Agent without actions, you may provide one top-level input_schema.
The translation action above uses:
{
"input_schema": {
"type": "object",
"properties": {
"text": {
"type": "string",
"description": "The source text to translate."
},
"target_language": {
"type": "string",
"description": "The language requested for the translated output.",
"enum": ["English", "Spanish", "French", "Japanese"]
},
"preserve_formatting": {
"type": "boolean",
"description": "Whether to preserve the source text's paragraph and list formatting."
}
},
"required": ["text", "target_language"],
"additionalProperties": false
}
}Each field has a specific purpose:
| Field or keyword | Meaning and Pond behavior |
|---|---|
type: "object" | The resulting parameters value is a JSON object. Every Pond V1 input_schema uses object at its root. |
properties | Declares the parameter names Pond may collect and send. |
Property type | Defines the JSON value Pond must collect and validate, such as string, integer, number, boolean, array, or object. |
Property description | Explains what the value means. Pond uses it to ask a useful follow-up question. Provide a clear description for every property. |
enum | Restricts a value to one of the listed choices. Pond uses those choices while clarifying and validating the request. |
| Validation constraints | Standard JSON Schema keywords such as minLength, maxLength, minimum, maximum, and items further constrain accepted values. |
required | Lists values Pond must collect before calling the Agent. Other declared properties remain optional. |
additionalProperties: false | Prevents Pond from sending undeclared keys. This value is required for Pond V1 input schemas. |
Do not confuse these related objects:
| Object | Purpose |
|---|---|
Manifest input_schema | Rules describing structured input the Agent accepts |
/runs.parameters | Actual user-specific values collected and validated using those rules |
The embedded /runs request schema | Rules for the complete request envelope, including action_id, messages, parameters, and execution |
How Pond selects and calls an action
Before calling an Agent that declares actions, Pond:
- Compares the prepared user intent with every action's
id,name, anddescription. - Selects exactly one matching action.
- Reads that action's
input_schemato identify required and optional values. - Asks follow-up questions for missing required values.
- Validates the collected values against the selected schema.
- Creates one synthesized user-role instruction in
messages. - Calls
POST /runsonce with the selectedaction_idand validatedparameters.
For the translation example, Pond constructs:
{
"action_id": "translate_text",
"parameters": {
"text": "Hello world",
"target_language": "Spanish",
"preserve_formatting": true
}
}When actions is absent, Pond omits action_id and uses the optional top-level input_schema. When the applicable schema is absent, Pond sends parameters: {} and relies on the synthesized instruction in messages.
If no declared action matches the user's request, Pond does not call the Agent. If an Agent declares actions, its server must reject a missing or undeclared action_id before accepting execution by returning HTTP 400 with unsupported_operation. The server must also validate parameters against the selected action's input_schema.
Add listing and pricing metadata
metadata is optional and does not determine protocol compatibility. We strongly recommend including complete listing and pricing information so Pond can prefill the publishing page.
Pond validates listing fields independently. Invalid scalar fields are ignored, and valid demo materials and FAQ entries are retained item by item. Pricing is imported only when pricing_plans is non-empty and every plan is valid, preventing a partial commercial offer from being imported.
| Metadata field | Accepted value |
|---|---|
name | Non-empty UTF-8; at most 255 characters and 255 UTF-8 bytes |
logo_url | Valid HTTP or HTTPS URL; at most 2,000 characters and 2,000 UTF-8 bytes |
short_description | Non-empty UTF-8; at most 500 characters and 500 UTF-8 bytes |
description | Detailed rich text; Pond sanitizes it before storage or rendering |
category | sales, content, development, analytics, research, workflow, compliance, productivity_tools, agent_infrastructure, or others |
demo_materials | Items containing HTTP(S) url, file_type, and non-empty file_name |
key_features | Rich text describing the Agent's main features |
use_cases | Rich text describing suitable uses |
setup_instructions | Setup guidance |
developer_x_url | Valid X profile URL |
developer_linkedin_url | Valid LinkedIn profile URL |
github_url | Valid public github.com repository URL |
faqs | Items with non-empty question and answer |
pricing_plans | Non-empty collection imported only when every plan is valid |
Remote metadata does not set the Access Key, Server Base URL, compliance confirmations, review state, or Pond-generated statistics. Missing publishing information must be entered manually before submitting the Agent for review.
Pricing plans
Pond's saved pricing configuration is the source of truth for billing. Manifest pricing plans prefill the publishing page but do not change a published price by themselves.
| Field | Meaning and usage |
|---|---|
name | Non-empty plan name shown to users |
pricing_model | free, pay_as_you_go, or subscription |
amount_minor | Price in US cents; 100 means $1.00 |
usage_quantity | For pay_as_you_go, how many units amount_minor buys; defaults to 1 |
usage_unit | token, result, or other |
custom_usage_unit | Concrete unit such as report or minute; required when usage_unit is other |
billing_interval | Subscription renewal interval; V1 supports month |
included_units | Positive allowance required for free and subscription plans |
validity_days | Optional duration for a free plan; 0 means no expiry |
description | Optional short explanation shown to users |
sort_order | Optional positive display position |
| Natural-language (NLP) pricing (coming soon; no V1 field yet) | Agent developers will be able to include a plain-text pricing description in the manifest. Pond will analyze it and convert it into structured pricing plans. Until this capability is announced, use metadata.pricing_plans. |
Do not provide Pond plan UUIDs, currency, or internal billing fields. Pond generates or derives them when the imported draft is saved.
Configure authentication
Configure the same secret Access Key in Pond and on the Agent server. Pond sends it to every Pond Protocol endpoint:
Authorization: Bearer <creator-configured-access-key>
X-Agent-Protocol-Version: 1.0The Agent server must verify the Bearer token before processing the request and return HTTP 401 unauthorized when it is missing, malformed, or incorrect. Never include the key in the manifest, URL, logs, errors, or example content.
The version header selects the exact Major.Minor protocol version. A missing or malformed version returns 400 invalid_request. A well-formed unsupported version returns 400 unsupported_protocol_version. Pond V1 supports exactly 1.0; 1.0.1 and 1.1 are not implied.
Receive a prepared run
Users first interact with Pond's Chat Interface. Pond may ask follow-up questions until it has the details required by the selected action and its input_schema. Only then does Pond send one prepared POST /runs request.
POST /runs
Authorization: Bearer <creator-configured-access-key>
Idempotency-Key: run_01JDEF
X-Agent-Protocol-Version: 1.0
Accept: application/json
Content-Type: application/jsonThe Idempotency-Key always equals the body run_id. The request does not contain the raw clarification transcript:
messagescontains exactly one Pond-generated user-role instruction synthesizing what the Agent should do.action_ididentifies the selected manifest action and is omitted when the manifest has noactions.parameterscontains actual structured values validated against the applicableinput_schema.- Relevant user uploads appear as file parts in
messagesonly whencapabilities.attachmentsistrue.
| Request field | Meaning |
|---|---|
run_id | Pond-created identifier for the complete logical execution; it matches the Idempotency-Key header |
agent_id | Identifies the Pond Agent being called |
conversation_id | Identifies the originating Pond chat for correlation; it does not grant access to the chat transcript |
history_truncated | Indicates whether Pond shortened non-required supporting context while preparing the instruction; required inputs remain present |
action_id | Exact selected manifest action; omitted when the manifest has no actions |
user | Pond-generated pseudonymous user information |
messages | Exactly one synthesized user-role execution message |
parameters | Actual structured values collected using the applicable input_schema |
execution | Requested response mode, accepted output modes, and deadline |
This complete request selects the translation action:
{
"run_id": "run_01JDEF",
"agent_id": "agt_01JXYZ",
"conversation_id": "chat_789",
"history_truncated": false,
"action_id": "translate_text",
"user": {
"id": "usr_pseudonymous_123",
"locale": "en-US",
"timezone": "America/Los_Angeles"
},
"messages": [
{
"id": "msg_01J001",
"role": "user",
"created_at": "2026-08-21T16:30:00Z",
"parts": [
{
"type": "text",
"text": "Translate 'Hello world' into Spanish and preserve its formatting."
}
]
}
],
"parameters": {
"text": "Hello world",
"target_language": "Spanish",
"preserve_formatting": true
},
"execution": {
"mode": "sync",
"accepted_output_modes": ["text/markdown"],
"deadline_ms": 300000
}
}Pond creates run_id, conversation_id, and the prepared message ID. The Agent creates identifiers only for objects it creates, such as asynchronous tasks and output artifacts. Echo the same run_id in every response.
/runs request JSON Schema
/runs request JSON SchemaThis complete Draft 2020-12 schema defines the request envelope. It permits action_id because the schema cannot see the discovered manifest. The behavioral rule is stricter: omit action_id when the manifest has no actions, and require an exact declared action ID when it does.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://docs.joinpond.ai/schemas/pond-run-request-v1.json",
"title": "Pond Run Request V1",
"type": "object",
"required": [
"run_id",
"agent_id",
"conversation_id",
"history_truncated",
"user",
"messages",
"parameters",
"execution"
],
"properties": {
"run_id": {
"description": "Pond-created identifier for one logical execution; matches Idempotency-Key.",
"type": "string",
"minLength": 1
},
"agent_id": {
"description": "Identifier of the Pond Agent being called.",
"type": "string",
"minLength": 1
},
"conversation_id": {
"description": "Identifier of the originating Pond chat for correlation.",
"type": "string",
"minLength": 1
},
"history_truncated": {
"description": "Whether Pond shortened non-required supporting context.",
"type": "boolean"
},
"action_id": {
"description": "Selected manifest action; omitted when the manifest declares no actions.",
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,63}$"
},
"user": {
"description": "Pond-generated pseudonymous user information.",
"$ref": "#/$defs/user"
},
"messages": {
"description": "Exactly one synthesized user-role execution message.",
"type": "array",
"minItems": 1,
"maxItems": 1,
"items": {
"$ref": "#/$defs/message"
}
},
"parameters": {
"description": "Values collected and validated against the applicable manifest input_schema.",
"type": "object"
},
"execution": {
"description": "Requested response mode, accepted outputs, and deadline.",
"$ref": "#/$defs/execution"
}
},
"additionalProperties": false,
"$defs": {
"user": {
"type": "object",
"required": ["id", "locale", "timezone"],
"properties": {
"id": {
"description": "Pond-generated pseudonymous user identifier.",
"type": "string",
"minLength": 1
},
"locale": {
"description": "User locale such as en-US.",
"type": "string",
"minLength": 1
},
"timezone": {
"description": "User timezone such as America/Los_Angeles.",
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"message": {
"type": "object",
"required": ["id", "role", "created_at", "parts"],
"properties": {
"id": {
"description": "Pond-created identifier for the prepared execution message.",
"type": "string",
"minLength": 1
},
"role": {
"description": "Always user in Pond Protocol V1.",
"const": "user"
},
"created_at": {
"description": "RFC 3339 time when Pond created the prepared message.",
"type": "string",
"format": "date-time"
},
"parts": {
"description": "Ordered synthesized text and relevant user-uploaded file parts.",
"type": "array",
"minItems": 1,
"contains": {
"$ref": "#/$defs/textPart"
},
"minContains": 1,
"items": {
"oneOf": [
{
"$ref": "#/$defs/textPart"
},
{
"$ref": "#/$defs/filePart"
}
]
}
}
},
"additionalProperties": false
},
"textPart": {
"type": "object",
"required": ["type", "text"],
"properties": {
"type": {
"description": "Text-part discriminator.",
"const": "text"
},
"text": {
"description": "Non-empty synthesized instruction text.",
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"filePart": {
"type": "object",
"required": ["type", "file"],
"properties": {
"type": {
"description": "File-part discriminator.",
"const": "file"
},
"file": {
"description": "Relevant user upload available through a signed HTTPS URL.",
"type": "object",
"required": ["url", "name", "media_type"],
"properties": {
"url": {
"description": "Signed HTTPS URL available through the execution deadline.",
"type": "string",
"format": "uri",
"pattern": "^https://"
},
"name": {
"description": "Original or Pond-assigned filename.",
"type": "string",
"minLength": 1
},
"media_type": {
"description": "Input media type also declared in manifest input_modes.",
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"execution": {
"type": "object",
"required": ["mode", "accepted_output_modes", "deadline_ms"],
"properties": {
"mode": {
"description": "Requested response mode supported by the manifest.",
"enum": ["sync", "stream", "async"]
},
"accepted_output_modes": {
"description": "Output media types Pond accepts for this run.",
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
},
"deadline_ms": {
"description": "Maximum execution duration in milliseconds from acceptance.",
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false
}
}
}The Agent must additionally validate that:
action_idfollows the discovered manifest rule.parametersmatches the selected action-level or top-levelinput_schema.execution.modeis advertised by the corresponding capability.accepted_output_modesoverlaps the manifestoutput_modes.deadline_msdoes not exceedlimits.max_run_seconds × 1000.- The request body and file parts stay within the declared limits.
Return a result
Start with synchronous execution. Add streaming or asynchronous execution only when needed.
| Mode | Manifest capability | Request Accept | Successful transport |
|---|---|---|---|
sync | capabilities.sync | application/json | HTTP 200 terminal result |
stream | capabilities.streaming | text/event-stream | HTTP 200 SSE stream |
async | capabilities.async_tasks | application/json | HTTP 202 task acceptance |
Synchronous
A completed result contains run_id, status, ordered output, and cumulative usage:
{
"run_id": "run_01JDEF",
"status": "completed",
"output": [
{
"type": "text",
"text": "Hola mundo"
}
],
"usage": {
"unit_of_measurement": "token",
"quantity": 42
}
}An accepted synchronous failure still returns HTTP 200, with status: "failed", a safe error object, and cumulative usage.
Streaming
Return HTTP 200 text/event-stream. The first event is run.started, followed by zero or more message.delta or artifact.ready events. End with exactly one run.completed or run.error event.
event: run.started
data: {"run_id":"run_01JDEF"}
event: message.delta
data: {"run_id":"run_01JDEF","part_index":0,"delta":"Hola "}
event: message.delta
data: {"run_id":"run_01JDEF","part_index":0,"delta":"mundo"}
event: run.completed
data: {"run_id":"run_01JDEF","output":[{"type":"text","text":"Hola mundo"}],"usage":{"unit_of_measurement":"token","quantity":42}}Pond Protocol V1 does not define SSE replay, reconnection, or stream resumption.
Asynchronous
Accept the run with HTTP 202:
{
"run_id": "run_01JDEF",
"task_id": "task_agent_123",
"status": "queued",
"poll_after_ms": 2000
}| Acceptance field | Requirement |
|---|---|
run_id | Required; echoes the Pond run ID |
task_id | Required stable ID created by the Agent server |
status | Required initial status, normally queued |
poll_after_ms | Optional scheduling hint; it does not guarantee the exact next poll time |
Pond polls GET /tasks/{task_id} with the same authorization and protocol-version headers until it receives a valid terminal result or reaches the deadline.
A running task may include progress:
{
"run_id": "run_01JDEF",
"task_id": "task_agent_123",
"status": "running",
"progress": {
"percent": 60,
"message": "Translating the text"
},
"updated_at": "2026-08-21T16:31:20Z"
}A completed task contains ordered output, referenced artifacts when present, and cumulative usage:
{
"run_id": "run_01JDEF",
"task_id": "task_agent_123",
"status": "completed",
"output": [
{
"type": "text",
"text": "Hola mundo"
}
],
"usage": {
"unit_of_measurement": "token",
"quantity": 42
},
"updated_at": "2026-08-21T16:32:10Z"
}| Status | Terminal | Required result |
|---|---|---|
queued | No | Task identity and status |
running | No | Task identity and status; optional progress |
completed | Yes | Valid output, referenced artifacts, and usage |
failed | Yes | Valid error and usage |
expired | Yes | Valid error and usage |
Pond continues polling after queued or running. Network failures, malformed JSON, invalid media types, and other invalid responses that do not establish a terminal status are non-terminal, so Pond continues until a valid terminal result or the deadline.
A completed task must contain valid output, a failed or expired task must contain a valid error, and every artifact reference must resolve. If the Agent reports a terminal status with invalid required output or error, Pond stops polling immediately and records invalid_agent_response. Invalid usage remains non-blocking and does not change an otherwise valid terminal result into invalid_agent_response. If the deadline is reached first, Pond stops polling and records a timeout.
Report usage
Every terminal response produced after execution begins contains cumulative usage:
{
"usage": {
"unit_of_measurement": "token",
"quantity": 10000
}
}unit_of_measurement is token, result, or other and must match the saved pricing plan. quantity is a non-negative integer for the complete logical run. When the plan uses other, Pond interprets it using the saved custom_usage_unit.
Usage is required on synchronous success and accepted failure, run.completed, run.error, and terminal asynchronous tasks. Request-level failures before execution begins do not include usage.
Missing, malformed, or mismatched usage does not hide an otherwise valid result. Pond records a metering error, uses zero variable units, and notifies the Agent creator.
Handle files
When capabilities.attachments is true, Pond may include relevant user uploads as file parts:
{
"type": "file",
"file": {
"url": "https://files.marketplace.example/signed/input.png",
"name": "input.png",
"media_type": "image/png"
}
}The signed HTTPS URL does not require the Pond Access Key or another authorization header and remains available through the execution deadline.
An Agent-generated file artifact has one content source: an external HTTPS url or complete Base64-encoded bytes. It also has an Agent-created id, name, and media_type.
External URL:
{
"id": "artifact_123",
"type": "file",
"file": {
"url": "https://agent.example.com/output/report.pdf",
"name": "report.pdf",
"media_type": "application/pdf"
}
}Inline bytes:
{
"id": "artifact_456",
"type": "file",
"file": {
"bytes": "RXhhbXBsZSByZXBvcnQuCg==",
"name": "report.txt",
"media_type": "text/plain"
}
}Pond displays an external URL but does not fetch or copy it. The URL must be available to the intended user without Agent credentials. Pond decodes and stores inline bytes.
Completed synchronous and asynchronous results put complete definitions in top-level artifacts and use artifact_ref entries in output. Streaming sends each complete definition in artifact.ready before the terminal event. Every reference must resolve to exactly one artifact definition.
Return errors
Use a stable machine-readable code and a safe English message:
{
"run_id": "run_01JDEF",
"error": {
"code": "invalid_input",
"message": "The source text cannot be empty.",
"details": {
"field": "parameters.text"
}
}
}code and message are required. details is optional. Never expose credentials, stack traces, database messages, or private infrastructure details.
The following statuses apply when a request is rejected before execution begins:
| Code | HTTP status | Meaning |
|---|---|---|
invalid_request | 400 | Malformed JSON or protocol validation failure |
idempotency_conflict | 409 | The key was reused with a materially different request |
unauthorized | 401 | Missing or invalid Access Key |
invalid_input | 422 | Valid request containing input the Agent cannot process |
unsupported_operation | 400 | Missing, unknown, or unsupported action or execution mode |
unsupported_protocol_version | 400 | Unsupported Pond Major.Minor version |
unsupported_content_type | 415 | Unsupported media type |
task_not_found | 404 | Asynchronous task does not exist or is inaccessible |
rate_limited | 429 | Agent server is rate-limiting requests |
temporarily_unavailable | 503 | Agent server cannot currently process the request |
internal_error | 500 | Unexpected Agent server failure |
Pond handles an unknown Agent error code as internal_error. Pond may independently record gateway conditions such as agent_timeout, agent_unreachable, and invalid_agent_response; Agent servers must not return those Pond-only codes.
After execution is accepted, put the error in that mode's terminal response: synchronous HTTP 200 with status: "failed", streaming run.error, or a terminal failed/expired asynchronous task.
Make runs idempotent
Pond sends Idempotency-Key: <run_id> and does not intentionally retry POST /runs. Protect against unintended duplicates:
- Coalesce concurrent requests with the same key and body.
- Return the saved terminal synchronous result for a completed duplicate.
- Return the original
task_idand current state for an asynchronous duplicate. - Return HTTP
409 idempotency_conflictwhen the same key is reused with a materially different request. - Retain the mapping through at least the execution deadline.
Test and publish
Before submitting the Agent:
- Deploy the HTTPS server and configure the same Access Key in Pond and on the server.
- Confirm
GET /manifestreturns a valid V1 manifest and rejects missing or incorrect authentication. - Review every listing and pricing value imported into the publishing page.
- Confirm Pond selects the correct action for representative user requests.
- Confirm Pond asks for missing required
input_schemavalues before dispatch. - Confirm
/runs.action_idand/runs.parametersmatch the selected manifest action. - Test every advertised execution mode and media type.
- Test safe failures before and after execution is accepted.
- Test user uploads and Agent-generated files when supported.
- Confirm every terminal result includes valid cumulative usage.
- Enter any required publishing information missing from the manifest.
- Submit the Agent for review.
Only advertise actions, capabilities, media modes, and limits that are deployed and ready for production traffic. Revalidate the Agent after changing any of them.
Appendix: Manifest validation schema
This appendix is for validation tooling. Agent developers do not need to copy, modify, or include this schema in GET /manifest. Write only the manifest described above, then use this schema with a JSON Schema-compatible editor, CI check, or validator to catch structural errors before submission. Pond can use the same schema when validating a submitted manifest.
The following complete JSON Schema uses Draft 2020-12. It defines which Pond Protocol V1 manifest fields are required or optional, the types and values they accept, and the rules for optional actions and input schemas.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://docs.joinpond.ai/schemas/pond-agent-manifest-v1.json",
"title": "Pond Agent Manifest V1",
"type": "object",
"required": [
"protocol",
"protocol_version",
"agent_version",
"capabilities",
"input_modes",
"output_modes",
"limits"
],
"properties": {
"protocol": {
"description": "Pond discovery protocol identifier.",
"const": "marketplace-agent"
},
"protocol_version": {
"description": "Exact Pond Protocol Major.Minor version.",
"const": "1.0"
},
"agent_version": {
"description": "Non-empty identifier for this Agent release.",
"type": "string",
"minLength": 1
},
"metadata": {
"description": "Optional marketplace data. Invalid values are ignored during field-level import and do not invalidate the protocol manifest.",
"type": "object",
"additionalProperties": true
},
"actions": {
"description": "Optional distinct operations Pond may select from user intent.",
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"$ref": "#/$defs/action"
}
},
"input_schema": {
"description": "Optional structured input contract for an Agent that does not declare actions.",
"$ref": "#/$defs/inputSchema"
},
"capabilities": {
"description": "Execution and content behavior supported by the Agent.",
"$ref": "#/$defs/capabilities"
},
"input_modes": {
"description": "Media types the Agent accepts from Pond.",
"$ref": "#/$defs/mediaModes"
},
"output_modes": {
"description": "Media types the Agent can return to Pond.",
"$ref": "#/$defs/mediaModes"
},
"limits": {
"description": "Request, attachment, and execution limits used before dispatch.",
"$ref": "#/$defs/limits"
}
},
"not": {
"required": ["actions", "input_schema"]
},
"additionalProperties": true,
"$defs": {
"action": {
"type": "object",
"required": ["id", "name", "description"],
"properties": {
"id": {
"description": "Stable unique action identifier returned in /runs.action_id.",
"type": "string",
"pattern": "^[a-z][a-z0-9_]{0,63}$"
},
"name": {
"description": "Concise developer-facing action name.",
"type": "string",
"minLength": 1,
"maxLength": 100
},
"description": {
"description": "Routing guidance describing when Pond should select this action.",
"type": "string",
"minLength": 1,
"maxLength": 1000
},
"input_schema": {
"description": "Optional structured values Pond collects for this action.",
"$ref": "#/$defs/inputSchema"
}
},
"additionalProperties": false
},
"inputSchema": {
"type": "object",
"required": ["type", "properties", "additionalProperties"],
"properties": {
"type": {
"description": "The /runs.parameters root is always a JSON object.",
"const": "object"
},
"properties": {
"description": "Named parameter definitions Pond may collect and send.",
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/valueSchema"
}
},
"required": {
"description": "Parameter names Pond must collect before dispatch.",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"additionalProperties": {
"description": "Must be false so Pond never sends undeclared parameters.",
"const": false
}
},
"additionalProperties": true
},
"valueSchema": {
"type": "object",
"required": ["type", "description"],
"properties": {
"type": {
"description": "Expected JSON value type.",
"enum": ["string", "integer", "number", "boolean", "array", "object"]
},
"description": {
"description": "Human-readable meaning Pond uses when collecting the value.",
"type": "string",
"minLength": 1
},
"enum": {
"description": "Optional fixed choices accepted for this value.",
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"minLength": {
"description": "Optional minimum string length.",
"type": "integer",
"minimum": 0
},
"maxLength": {
"description": "Optional maximum string length.",
"type": "integer",
"minimum": 0
},
"minimum": {
"description": "Optional inclusive numeric minimum.",
"type": "number"
},
"maximum": {
"description": "Optional inclusive numeric maximum.",
"type": "number"
},
"items": {
"description": "Schema for each value in an array parameter.",
"$ref": "#/$defs/valueSchema"
},
"properties": {
"description": "Nested definitions for an object parameter.",
"type": "object",
"additionalProperties": {
"$ref": "#/$defs/valueSchema"
}
},
"required": {
"description": "Required keys for an object parameter.",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"additionalProperties": {
"description": "Whether an object parameter may contain undeclared keys.",
"type": "boolean"
}
},
"additionalProperties": true
},
"importableMetadata": {
"type": "object",
"properties": {
"name": {
"description": "Agent name shown on Pond.",
"type": "string",
"minLength": 1,
"maxLength": 255
},
"logo_url": {
"description": "HTTP or HTTPS logo URL used on the publishing page.",
"type": "string",
"format": "uri",
"pattern": "^https?://",
"maxLength": 2000
},
"short_description": {
"description": "Short Agent summary shown in listings.",
"type": "string",
"minLength": 1,
"maxLength": 500
},
"description": {
"description": "Detailed Agent description; Pond sanitizes rich text.",
"type": "string",
"minLength": 1
},
"category": {
"description": "Pond marketplace category.",
"enum": [
"sales",
"content",
"development",
"analytics",
"research",
"workflow",
"compliance",
"productivity_tools",
"agent_infrastructure",
"others"
]
},
"demo_materials": {
"description": "Optional examples shown on the publishing page.",
"type": "array",
"items": {
"$ref": "#/$defs/importableDemoMaterial"
}
},
"key_features": {
"description": "Rich text describing the Agent's main features.",
"type": "string",
"minLength": 1
},
"use_cases": {
"description": "Rich text describing suitable use cases.",
"type": "string",
"minLength": 1
},
"setup_instructions": {
"description": "Instructions users should follow before using the Agent.",
"type": "string",
"minLength": 1
},
"developer_x_url": {
"description": "Developer's X profile URL.",
"type": "string",
"format": "uri",
"pattern": "^https://(www\\.)?(x\\.com|twitter\\.com)/"
},
"developer_linkedin_url": {
"description": "Developer's LinkedIn profile URL.",
"type": "string",
"format": "uri",
"pattern": "^https://(www\\.)?linkedin\\.com/"
},
"github_url": {
"description": "Public GitHub repository URL for the Agent.",
"type": "string",
"format": "uri",
"pattern": "^https://github\\.com/[^/]+/[^/]+/?$"
},
"faqs": {
"description": "Frequently asked questions shown on the publishing page.",
"type": "array",
"items": {
"$ref": "#/$defs/importableFaq"
}
},
"pricing_plans": {
"description": "Structured pricing plans imported atomically into the publishing draft.",
"type": "array",
"minItems": 1,
"items": {
"$ref": "#/$defs/importablePricingPlan"
}
}
},
"additionalProperties": true
},
"importableDemoMaterial": {
"type": "object",
"required": ["url", "file_type", "file_name"],
"properties": {
"url": {
"description": "Public HTTP or HTTPS demo asset URL.",
"type": "string",
"format": "uri",
"pattern": "^https?://"
},
"file_type": {
"description": "Demo asset presentation type.",
"enum": ["image", "video", "pdf", "ppt", "pptx"]
},
"file_name": {
"description": "Non-empty display filename.",
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"importableFaq": {
"type": "object",
"required": ["question", "answer"],
"properties": {
"question": {
"description": "FAQ question.",
"type": "string",
"minLength": 1
},
"answer": {
"description": "FAQ answer; Pond sanitizes rich text.",
"type": "string",
"minLength": 1
}
},
"additionalProperties": false
},
"importablePricingPlan": {
"type": "object",
"required": ["name", "pricing_model", "amount_minor", "usage_unit"],
"properties": {
"name": {
"description": "Plan name shown to users.",
"type": "string",
"minLength": 1
},
"pricing_model": {
"description": "How users are charged.",
"enum": ["free", "pay_as_you_go", "subscription"]
},
"amount_minor": {
"description": "Price in minor USD units; 100 means $1.00.",
"type": "integer",
"minimum": 0
},
"usage_quantity": {
"description": "For pay-as-you-go pricing, the number of units purchased for amount_minor.",
"type": "integer",
"minimum": 1,
"default": 1
},
"usage_unit": {
"description": "Unit the Agent reports in terminal usage.",
"enum": ["token", "result", "other"]
},
"custom_usage_unit": {
"description": "Concrete unit required when usage_unit is other.",
"type": "string",
"minLength": 1
},
"billing_interval": {
"description": "Subscription renewal interval.",
"const": "month"
},
"included_units": {
"description": "Positive allowance included with a free or subscription plan.",
"type": "integer",
"minimum": 1
},
"validity_days": {
"description": "Optional free-plan validity; zero means no expiry.",
"type": "integer",
"minimum": 0
},
"description": {
"description": "Optional plan explanation shown to users.",
"type": "string"
},
"sort_order": {
"description": "Optional positive display position.",
"type": "integer",
"minimum": 1
}
},
"allOf": [
{
"if": {
"properties": {
"usage_unit": {
"const": "other"
}
},
"required": ["usage_unit"]
},
"then": {
"required": ["custom_usage_unit"]
},
"else": {
"not": {
"required": ["custom_usage_unit"]
}
}
},
{
"if": {
"properties": {
"pricing_model": {
"const": "free"
}
},
"required": ["pricing_model"]
},
"then": {
"required": ["included_units"],
"properties": {
"amount_minor": {
"const": 0
}
},
"not": {
"anyOf": [
{
"required": ["usage_quantity"]
},
{
"required": ["billing_interval"]
}
]
}
}
},
{
"if": {
"properties": {
"pricing_model": {
"const": "pay_as_you_go"
}
},
"required": ["pricing_model"]
},
"then": {
"properties": {
"amount_minor": {
"minimum": 1
}
},
"not": {
"anyOf": [
{
"required": ["billing_interval"]
},
{
"required": ["included_units"]
},
{
"required": ["validity_days"]
}
]
}
}
},
{
"if": {
"properties": {
"pricing_model": {
"const": "subscription"
}
},
"required": ["pricing_model"]
},
"then": {
"required": ["billing_interval", "included_units"],
"properties": {
"amount_minor": {
"minimum": 1
}
},
"not": {
"anyOf": [
{
"required": ["usage_quantity"]
},
{
"required": ["validity_days"]
}
]
}
}
}
],
"additionalProperties": false
},
"capabilities": {
"type": "object",
"required": [
"sync",
"streaming",
"async_tasks",
"cancellation",
"attachments",
"feedback"
],
"properties": {
"sync": {
"description": "Supports synchronous JSON terminal results.",
"type": "boolean"
},
"streaming": {
"description": "Supports Server-Sent Events streaming.",
"type": "boolean"
},
"async_tasks": {
"description": "Supports asynchronous task acceptance and polling.",
"type": "boolean"
},
"cancellation": {
"description": "Reserved in V1 and must be false.",
"const": false
},
"attachments": {
"description": "Accepts user-uploaded file parts from Pond.",
"type": "boolean"
},
"feedback": {
"description": "Reserved in V1 and must be false.",
"const": false
}
},
"anyOf": [
{
"properties": {
"sync": {
"const": true
}
}
},
{
"properties": {
"streaming": {
"const": true
}
}
},
{
"properties": {
"async_tasks": {
"const": true
}
}
}
],
"additionalProperties": false
},
"mediaModes": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "string",
"minLength": 1
}
},
"limits": {
"type": "object",
"required": [
"max_request_bytes",
"max_attachment_bytes",
"max_run_seconds"
],
"properties": {
"max_request_bytes": {
"description": "Maximum /runs request-body size accepted by the Agent.",
"type": "integer",
"minimum": 1
},
"max_attachment_bytes": {
"description": "Maximum size of one user-uploaded input attachment.",
"type": "integer",
"minimum": 1
},
"max_run_seconds": {
"description": "Maximum execution duration accepted by the Agent.",
"type": "integer",
"minimum": 1
}
},
"additionalProperties": false
}
}
}The outer schema intentionally validates metadata only as an object. Pond evaluates its fields independently during publishing-page import: invalid scalar values and invalid demo or FAQ items are ignored, while an invalid pricing collection is omitted as a whole. The importableMetadata definitions document importable shapes but are not part of protocol compatibility.
The schema validates action objects but cannot enforce unique values of actions[].id. Action IDs must still be unique within the manifest.
Updated 1 day ago
