Services
How an API describes itself so agents can discover and call it — an index and one file per action, served statically. Specification v1.
Status v1 · Discovery root /.well-known/interagentic/
A service describes itself with static JSON. Clients fetch the index to learn what exists, then fetch one action file to learn how to call it. Nothing is generated at request time, so every file can be built once and served from a CDN.
1. Files
| Path | Contents |
|---|---|
/.well-known/interagentic/services.json | Index — identity, groups, action summaries |
/.well-known/interagentic/services/<group>/<action>.json | One action — its arguments, how they map onto the request, and an example |
An action file's path mirrors its command, one directory per segment:
["orders", "refunds", "create"] lives at services/orders/refunds/create.json.
Every file MUST be served over HTTPS with Content-Type: application/json.
Clients SHOULD cache them and MAY probe the index with HEAD to test
compatibility.
2. Index
{
"name": "Popcorn Club",
"description": "Popcorn, delivered.",
"version": "1",
"auth": "interagentic",
"baseUrl": "/api/v1",
"groups": [
{ "command": ["orders"], "description": "Place and track orders" }
],
"actions": [
{
"command": ["orders", "create"],
"endpoint": "/orders",
"method": "POST",
"description": "Place an order",
"role": "user",
"payment": "fixed"
}
]
}| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Human-readable service name |
description | string | yes | One line |
version | string | yes | "1" |
auth | string | yes | "interagentic" — the service verifies agent tokens |
baseUrl | string | yes | Fixed prefix for every endpoint, e.g. /api/v1 |
groups | array | no | Descriptions for intermediate command nodes |
actions | array | yes | Action summaries |
The caller's identity travels in the JWT, so it MUST NOT appear in baseUrl or
in an endpoint.
Action summary
| Field | Type | Required | Notes |
|---|---|---|---|
command | string[] | yes | Subcommand chain. See the command model. |
endpoint | string | yes | Appended to baseUrl. May contain templates. |
method | string | yes | GET, POST, PUT, PATCH or DELETE |
description | string | yes | Help text for this command |
role | string | no | user (default) or admin |
payment | string | yes | free, fixed, budget or provider_access |
3. Action detail
{
"command": ["orders", "create"],
"endpoint": "/orders",
"method": "POST",
"description": "Place an order",
"role": "user",
"payment": { "mode": "fixed", "amount": 9.0 },
"args": ["qty"],
"params": [
{
"name": "qty",
"label": "Quantity",
"description": "How many bags to send",
"example": 2,
"required": true,
"type": { "type": "integer", "minimum": 1 }
}
],
"example": {
"input": { "qty": 2 },
"output": { "id": "ord_8fJ2", "status": "confirmed" }
}
}Parameters
params declares the arguments a caller can give — what they are called, what
type they have, and whether they are required. Where each one ends up in the
HTTP request is a separate concern, described by
request templates.
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | The argument's name, and its --flag |
label | string | yes | Human-readable label |
description | string | yes | What it does |
required | boolean | yes | |
type | object | yes | A JSON Schema fragment |
example | any | no | Used in generated help |
placeholder | string | no | For form rendering |
args lists parameter names that may be given positionally, in order. Anything
not in args is a named flag.
Files
When an action accepts uploads, a top-level files object describes the rules:
{
"bodyKey": "files",
"format": { "path": "string", "contents": "string | base64DataString" },
"dirs": false,
"multiple": true,
"maxFiles": null,
"extensions": { "whitelist": [".png", ".jpg"], "blacklist": [] }
}4. Request templates
Templates map a command's arguments onto the HTTP request. They are what let an API whose shape does not match its arguments one-to-one — a path parameter with a different name, a nested body, a query flag — be described without changing the API.
{
"command": ["orders", "refunds", "create"],
"endpoint": "/orders/{{$id}}/refunds",
"method": "POST",
"args": ["id"],
"params": [
{ "name": "id", "label": "Order", "description": "The order to refund", "required": true, "type": { "type": "string" } },
{ "name": "reason", "label": "Reason", "description": "Why it is refunded", "required": true, "type": { "type": "string", "enum": ["damaged", "late", "other"] } },
{ "name": "amount", "label": "Amount", "description": "Partial refund. Omit to refund in full.", "required": false, "type": { "type": "number" } },
{ "name": "notify", "label": "Notify", "description": "Email the customer", "required": false, "type": { "type": "boolean" } }
],
"request": {
"query": { "notify": "{{$notify}}" },
"body": {
"reason": "{{$reason}}",
"refund": { "amount": "{{$amount}}" }
}
}
}npx interagentic popcorn.club orders refunds create ord_8fJ2 \
--reason damaged --amount 4.5 --notifyPOST /api/v1/orders/ord_8fJ2/refunds?notify=true
Content-Type: application/json
{ "reason": "damaged", "refund": { "amount": 4.5 } }Syntax
{{$name}} is the value of argument name. {{$name.a.b}} reads a path inside
a structured argument — one whose type is an object, given as JSON:
"body": {
"shipping_city": "{{$address.city}}",
"shipping_zip": "{{$address.zip}}"
}npx interagentic popcorn.club orders create --qty 2 \
--address '{"city": "Paris", "zip": "75001"}'Templates always begin with $. The unprefixed {{NAME}} form is a credential
placeholder, which only the proxy in Secure calls
interprets; a client never substitutes it.
Rules
- Where templates may appear. In
endpoint, and in any string value ofrequest.query,request.headersandrequest.body, at any depth. - Types are preserved. A value that is exactly one template takes the
argument's typed value —
"{{$amount}}"becomes the number4.5, not the string"4.5". A template inside a longer string is interpolated as text. - Escaping. Values substituted into
endpointorrequest.queryare URL-encoded. Values inrequest.bodyare JSON-encoded. - Missing arguments. When an optional argument is not given, every key
whose value uses it is omitted — nothing is sent as
nullor as an empty string. Arguments used inendpointMUST be required. requestis complete when present. If an action has arequestobject, arguments it does not reference are not sent.- Defaults when absent. Without a
requestobject, arguments not used inendpointare sent under their own names — as a JSON body forPOST,PUTandPATCH, and as query parameters forGETandDELETE. Most actions need no templates at all. - Reserved headers.
Authorizationis set by the client and MUST NOT be templated.
Literals are allowed anywhere a template is, so constants need no argument:
"request": {
"headers": { "X-Api-Version": "2026-09" },
"body": { "channel": "agent", "qty": "{{$qty}}" }
}5. Payment modes
| Mode | Meaning |
|---|---|
free | No charge |
fixed | A known price, given in the action detail's payment.amount |
budget | Priced at run time, within a limit the human approved |
provider_access | Access to a third-party provider the service resells |
See Credits for what a service returns when payment is required.
6. Calling
<method> https://<domain><baseUrl><endpoint>
Authorization: Bearer <agent jwt>The client validates the arguments against params, renders endpoint and
request as described above, and sends the result. A command whose arguments
fail validation is rejected before any request is made.
Response rendering
Clients append ?humanReadable=true unless asked for raw output. A service MAY
answer that with Content-Type: text/markdown, which clients render as
formatted terminal output. Without the parameter, the service returns its normal
JSON.
npx interagentic popcorn.club orders list # markdown, rendered
npx interagentic popcorn.club orders list --json # raw JSONThis lets one endpoint serve both an agent parsing fields and a person reading a terminal, without a second API.
7. Errors
Services SHOULD return errors as:
{
"error": {
"code": "INVALID_QUANTITY",
"message": "qty must be at least 1",
"details": { "field": "qty" }
}
}Clients surface code and message and exit non-zero.