@stellar-agent-services/dev-mcp 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -2
- package/dist/docs.d.ts +35 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +92 -0
- package/dist/docs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +125 -0
- package/dist/server.js.map +1 -0
- package/package.json +40 -4
- package/repository/AGENTS.md +162 -0
- package/repository/CONTRIBUTING.md +95 -0
- package/repository/IMPLEMENTATION_PLAN.md +560 -0
- package/repository/README.md +80 -0
- package/repository/SECURITY.md +22 -0
- package/repository/apps/wallet/README.md +78 -0
- package/repository/docs/client-integration/README-AI.md +495 -0
- package/repository/docs/client-integration/README.md +138 -0
- package/repository/docs/dx-improvements.md +670 -0
- package/repository/docs/erc-compatibility.md +163 -0
- package/repository/docs/job-recovery.md +201 -0
- package/repository/docs/payment-client.md +290 -0
- package/repository/docs/provider-setup-guide.md +348 -0
- package/repository/docs/provider-standard.md +728 -0
- package/repository/docs/recovery/README-AI.md +288 -0
- package/repository/docs/recovery/README.md +119 -0
- package/repository/docs/sow-closeout.md +67 -0
- package/repository/docs/standard/README-AI.md +614 -0
- package/repository/docs/standard/README.md +108 -0
- package/repository/docs/verification.md +504 -0
- package/repository/examples/paid-service/README.md +33 -0
- package/repository/examples/python-upstream/README.md +44 -0
- package/repository/llms.txt +65 -0
- package/repository/packages/create-stellar-service/README.md +143 -0
- package/repository/packages/dev-mcp/README.md +51 -0
- package/repository/packages/payment-client/README.md +98 -0
- package/repository/packages/protocol/README.md +15 -0
- package/repository/packages/protocol/schemas/manifest.schema.json +272 -0
- package/repository/packages/protocol/test-vectors/README.md +45 -0
- package/repository/packages/provider-kit/README.md +509 -0
- package/repository/packages/provider-kit/schemas/gateway-config.schema.json +140 -0
- package/repository/packages/verifier/README.md +131 -0
- package/repository/plugins/stellar-agent-services/README.md +31 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
# Provider Standard v1
|
|
2
|
+
|
|
3
|
+
> **Status: frozen** (2026-10-21, SOW Deliverable 1). v1 now only gains optional fields and SHOULD rules.
|
|
4
|
+
> Identifier: `stellar-agent-services/provider@1`.
|
|
5
|
+
>
|
|
6
|
+
> **Added since the freeze:** capacity and queuing (§5.8), 2026-10-23. Optional fields and SHOULD rules only.
|
|
7
|
+
|
|
8
|
+
This standard describes how a paid AI service on Stellar publishes its purpose, input, output, price, network,
|
|
9
|
+
job status and result. Any client can then find it, pay for it once and collect the result, even if the
|
|
10
|
+
connection drops.
|
|
11
|
+
|
|
12
|
+
It works with any language or stack. The Provider Kit is optional: a provider can implement this document
|
|
13
|
+
directly, or put the kit's gateway in front of an existing API.
|
|
14
|
+
|
|
15
|
+
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are used as in RFC 2119. Each rule has an id, such as
|
|
16
|
+
**[M3]**, which verification receipts cite.
|
|
17
|
+
|
|
18
|
+
## Contents
|
|
19
|
+
|
|
20
|
+
1. [Overview](#1-overview)
|
|
21
|
+
2. [Manifest](#2-manifest)
|
|
22
|
+
3. [Services](#3-services)
|
|
23
|
+
4. [Payment](#4-payment)
|
|
24
|
+
5. [Jobs](#5-jobs)
|
|
25
|
+
6. [Errors](#6-errors)
|
|
26
|
+
7. [Stellar 8004 identity](#7-stellar-8004-identity)
|
|
27
|
+
8. [Conformance levels](#8-conformance-levels)
|
|
28
|
+
9. [Versioning and extensions](#9-versioning-and-extensions)
|
|
29
|
+
10. [Security considerations](#10-security-considerations)
|
|
30
|
+
11. [Not in v1](#11-not-in-v1)
|
|
31
|
+
12. [Appendix A: example exchange](#appendix-a-example-exchange)
|
|
32
|
+
13. [Appendix B: mapping a service to an MCP tool](#appendix-b-mapping-a-service-to-an-mcp-tool)
|
|
33
|
+
|
|
34
|
+
## 1. Overview
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Stellar 8004 registration ──▶ manifest ──▶ create-job ──402──▶ pay (x402) ──▶ job
|
|
38
|
+
│
|
|
39
|
+
check-status / get-result ◀─────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
1. The provider's Stellar 8004 registration points to its **manifest**.
|
|
43
|
+
2. The manifest lists **services**, each with a price and input and output schemas.
|
|
44
|
+
3. The client calls **create-job**. The provider answers `402 Payment Required` with an x402 challenge.
|
|
45
|
+
4. The client pays and repeats the request. The provider returns the job: finished (`200`) or still running
|
|
46
|
+
(`202`).
|
|
47
|
+
5. If the response is lost, the client uses **check-status** and **get-result**. It never pays again.
|
|
48
|
+
|
|
49
|
+
### Built on
|
|
50
|
+
|
|
51
|
+
| Concern | Standard |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| Payment | [x402 v2](https://github.com/coinbase/x402), `exact` scheme on Stellar |
|
|
54
|
+
| Input and output | JSON Schema 2020-12, with MCP tool field names |
|
|
55
|
+
| Idempotency | [IETF Idempotency-Key](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header) (draft-07) |
|
|
56
|
+
| Long-running work | HTTP `202 Accepted` with `Location` and `Retry-After` |
|
|
57
|
+
| Load | HTTP `429 Too Many Requests` (RFC 6585) and `503 Service Unavailable` with `Retry-After` (RFC 9110) |
|
|
58
|
+
| Errors | RFC 9457 problem details |
|
|
59
|
+
| Hashing | SHA-256 over RFC 8785 canonical JSON (JCS) |
|
|
60
|
+
| Identity | Stellar 8004 (ERC-8004) registration file |
|
|
61
|
+
|
|
62
|
+
Only three things are specific to this standard:
|
|
63
|
+
- the manifest
|
|
64
|
+
- the job token
|
|
65
|
+
- the rules that bind one job to one request and one payment
|
|
66
|
+
|
|
67
|
+
### Ways to implement
|
|
68
|
+
|
|
69
|
+
| Path | For | What the provider writes |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| **Gateway** | any existing HTTP API, in any language | A config file. The gateway handles the manifest, payment and jobs, then forwards the input to the upstream API. |
|
|
72
|
+
| **TypeScript library** | new services on Node, Workers, Deno or Bun | `defineService(...)` plus a handler function |
|
|
73
|
+
| **Native** | any language | This document. The [manifest JSON Schema](../packages/protocol/schemas/manifest.schema.json), the [test vectors](../packages/protocol/test-vectors) and `verifier check` confirm conformance. |
|
|
74
|
+
|
|
75
|
+
## 2. Manifest
|
|
76
|
+
|
|
77
|
+
### 2.1 Location
|
|
78
|
+
|
|
79
|
+
- **[M1]** The manifest MUST be served over HTTPS with status `200` and `Content-Type: application/json`.
|
|
80
|
+
- **[M2]** It MAY be at any URL. The default is `/.well-known/stellar-agent-services.json` on the provider's
|
|
81
|
+
origin. The Stellar 8004 registration points to the actual URL (§7).
|
|
82
|
+
- **[M3]** Clients MUST NOT follow redirects when fetching the manifest. The manifest MUST be at most 256 KiB and
|
|
83
|
+
MUST be I-JSON (RFC 7493): UTF-8, no duplicate keys and no lone surrogates.
|
|
84
|
+
- **[M4]** Providers SHOULD send `Cache-Control: max-age` of 300 seconds or less.
|
|
85
|
+
|
|
86
|
+
### 2.2 Example
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"standard": "stellar-agent-services/provider@1",
|
|
91
|
+
"provider": {
|
|
92
|
+
"name": "Example Text Tools",
|
|
93
|
+
"description": "Small, deterministic text utilities for agents.",
|
|
94
|
+
"homepage": "https://example.com",
|
|
95
|
+
"agentId": 42,
|
|
96
|
+
"termsUrl": "https://example.com/terms",
|
|
97
|
+
"dataUse": { "storesInputs": false, "trainsOnInputs": false }
|
|
98
|
+
},
|
|
99
|
+
"baseUrl": "https://api.example.com/agent",
|
|
100
|
+
"payment": {
|
|
101
|
+
"protocol": "x402",
|
|
102
|
+
"x402Version": 2,
|
|
103
|
+
"accepts": [
|
|
104
|
+
{
|
|
105
|
+
"scheme": "exact",
|
|
106
|
+
"network": "stellar:testnet",
|
|
107
|
+
"asset": "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
|
|
108
|
+
"payTo": "GBZSBASBFMV5RGKPRIPCB5QQVAJ35IXKMTF2A2766MOA4ZHQ7MLSIPXU",
|
|
109
|
+
"maxTimeoutSeconds": 60,
|
|
110
|
+
"extra": { "areFeesSponsored": true }
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"jobs": { "retentionSeconds": 604800 },
|
|
115
|
+
"services": [
|
|
116
|
+
{
|
|
117
|
+
"name": "text-stats",
|
|
118
|
+
"title": "Text statistics",
|
|
119
|
+
"description": "Counts words, sentences and characters in English text. Use it to check length limits before publishing. Accepts up to 20,000 characters.",
|
|
120
|
+
"price": { "amount": "100000" },
|
|
121
|
+
"charge": "on-success",
|
|
122
|
+
"expectedDurationSeconds": 2,
|
|
123
|
+
"inputSchema": {
|
|
124
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
125
|
+
"type": "object",
|
|
126
|
+
"properties": { "text": { "type": "string", "maxLength": 20000 } },
|
|
127
|
+
"required": ["text"],
|
|
128
|
+
"additionalProperties": false
|
|
129
|
+
},
|
|
130
|
+
"outputSchema": {
|
|
131
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"words": { "type": "integer" },
|
|
135
|
+
"sentences": { "type": "integer" },
|
|
136
|
+
"characters": { "type": "integer" }
|
|
137
|
+
},
|
|
138
|
+
"required": ["words", "sentences", "characters"]
|
|
139
|
+
},
|
|
140
|
+
"examples": [
|
|
141
|
+
{ "input": { "text": "Hello world." }, "output": { "words": 2, "sentences": 1, "characters": 12 } }
|
|
142
|
+
],
|
|
143
|
+
"tags": ["text", "analysis"]
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The `asset` above is the testnet USDC Stellar Asset Contract. Implementations take it from `@x402/stellar`
|
|
150
|
+
(`USDC_TESTNET_ADDRESS`, `USDC_PUBNET_ADDRESS`) rather than retyping it. The example addresses are made from
|
|
151
|
+
hashes, so nobody holds their secret keys.
|
|
152
|
+
|
|
153
|
+
### 2.3 Fields
|
|
154
|
+
|
|
155
|
+
Required fields are in **bold**.
|
|
156
|
+
|
|
157
|
+
| Field | Type | Rules |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| **`standard`** | string | `stellar-agent-services/provider@1` |
|
|
160
|
+
| **`provider.name`** | string | 1–120 characters, no control characters |
|
|
161
|
+
| `provider.description` | string | ≤ 500 characters; tabs and newlines allowed, no other control characters |
|
|
162
|
+
| `provider.homepage` | URL | HTTPS |
|
|
163
|
+
| `provider.agentId` | integer | Stellar 8004 agent id (u32). Needed for the identity check (§7). |
|
|
164
|
+
| `provider.termsUrl` | URL | HTTPS |
|
|
165
|
+
| `provider.dataUse.storesInputs` | boolean | Whether inputs are kept after the job finishes |
|
|
166
|
+
| `provider.dataUse.trainsOnInputs` | boolean | Whether inputs are used to train models |
|
|
167
|
+
| **`baseUrl`** | URL | Job endpoints live under it (§5.1) |
|
|
168
|
+
| **`payment.protocol`** | string | `x402` |
|
|
169
|
+
| **`payment.x402Version`** | integer | `2` |
|
|
170
|
+
| **`payment.accepts`** | array | Exactly one entry in v1 (§4.1) |
|
|
171
|
+
| `jobs.retentionSeconds` | integer | How long results stay retrievable. Default 86400, minimum 3600. |
|
|
172
|
+
| **`services`** | array | 1–25 services (§3) |
|
|
173
|
+
| `extensions` | object | See §9 |
|
|
174
|
+
|
|
175
|
+
- **[M5]** `baseUrl` MUST be HTTPS and MUST have the same origin as the manifest URL. It MUST NOT have user
|
|
176
|
+
info, a query, a fragment or an empty path segment, and MUST NOT end with `/`.
|
|
177
|
+
- **[M6]** Every URL in the manifest MUST be HTTPS, at most 300 characters and without user info. A development
|
|
178
|
+
server on a loopback address MAY use `http://`; only clients and verifiers explicitly put in a development mode
|
|
179
|
+
accept it.
|
|
180
|
+
- **[M7]** Service `name` values MUST be unique within the manifest.
|
|
181
|
+
- **[M8]** Clients MUST ignore fields they don't recognize. Clients MUST reject a manifest whose `standard` has a
|
|
182
|
+
different major version.
|
|
183
|
+
- **[M9]** The manifest MUST be valid against the
|
|
184
|
+
[manifest JSON Schema](../packages/protocol/schemas/manifest.schema.json). It encodes the field tables in §2.3,
|
|
185
|
+
§3.1 and §4.1, and rules M5, M6, M8, P1, S4 and S5.
|
|
186
|
+
|
|
187
|
+
### 2.4 Validating a manifest
|
|
188
|
+
|
|
189
|
+
A client, verifier or kit validates a manifest in three steps:
|
|
190
|
+
|
|
191
|
+
1. **Fetch** ([M1], [M3]): HTTPS, status `200`, no redirects, at most 256 KiB.
|
|
192
|
+
2. **JSON Schema** ([M9]): any JSON Schema 2020-12 validator. Format assertion is not needed.
|
|
193
|
+
3. **Rules JSON Schema can't express:** the manifest URL is HTTPS [M1], no lone surrogates [M3], `baseUrl` has
|
|
194
|
+
the manifest's origin [M5], unique service names [M7], schema sizes [S6], examples that match their schemas
|
|
195
|
+
[S9] and the `payTo` checksum [P11]. `checkManifestRules()` in `packages/protocol` implements them.
|
|
196
|
+
|
|
197
|
+
The [test vectors](../packages/protocol/test-vectors) cover every step. They also cover canonical JSON, binding
|
|
198
|
+
hashes, header formats and challenge checks.
|
|
199
|
+
|
|
200
|
+
## 3. Services
|
|
201
|
+
|
|
202
|
+
### 3.1 Fields
|
|
203
|
+
|
|
204
|
+
| Field | Type | Rules |
|
|
205
|
+
| --- | --- | --- |
|
|
206
|
+
| **`name`** | string | Matches `^[a-z0-9][a-z0-9_-]{0,63}$`. Also used in URLs, MCP tool names and LLM function names. |
|
|
207
|
+
| `title` | string | 1–120 characters, no control characters |
|
|
208
|
+
| **`description`** | string | 1–1000 characters (§3.4); tabs and newlines allowed, no other control characters |
|
|
209
|
+
| **`price.amount`** | string | A positive integer in the asset's base units, at most 38 digits. USDC has 7 decimals, so `"100000"` is 0.01 USDC. |
|
|
210
|
+
| `charge` | string | `on-success` or `upfront` (§3.2). Default `upfront`. |
|
|
211
|
+
| `expectedDurationSeconds` | integer | Typical time to finish, at least 1. Required when `charge` is `on-success`. |
|
|
212
|
+
| **`inputSchema`** | object | JSON Schema 2020-12 (§3.3) |
|
|
213
|
+
| **`outputSchema`** | object | JSON Schema 2020-12 (§3.3) |
|
|
214
|
+
| `examples` | array | Up to 5 `{ input, output }` pairs. **[S9]** Each `input` MUST validate against `inputSchema` and each `output` against `outputSchema`. |
|
|
215
|
+
| `maxInputBytes` | integer | Request body limit. Default 1 MiB (1,048,576), maximum 10 MiB. |
|
|
216
|
+
| `tags` | array | Up to 10 strings matching `^[a-z0-9-]{1,32}$` |
|
|
217
|
+
| `capacity` | object | How much load the service takes: `concurrency` and `maxQueueWaitSeconds` (§5.8) |
|
|
218
|
+
| `extensions` | object | See §9 |
|
|
219
|
+
|
|
220
|
+
There is no sync or async flag. Every service uses the same job flow (§5), and `expectedDurationSeconds` is the
|
|
221
|
+
hint.
|
|
222
|
+
|
|
223
|
+
### 3.2 When the user is charged
|
|
224
|
+
|
|
225
|
+
| `charge` | Order | If the work fails |
|
|
226
|
+
| --- | --- | --- |
|
|
227
|
+
| `on-success` | verify payment → do the work → settle | Not charged |
|
|
228
|
+
| `upfront` | verify payment → settle → do the work | Charged. v1 has no refunds. |
|
|
229
|
+
|
|
230
|
+
- **[S1]** For `on-success`, the provider MUST settle only if the output is valid against `outputSchema`.
|
|
231
|
+
- **[S2]** A signed Stellar x402 payment expires about `maxTimeoutSeconds` after the client signs it. For
|
|
232
|
+
`on-success`, if the work and settlement don't finish before then, the job fails and the client is not charged.
|
|
233
|
+
Providers SHOULD keep `expectedDurationSeconds` at or below half of `maxTimeoutSeconds`. Longer work SHOULD use
|
|
234
|
+
`upfront`.
|
|
235
|
+
- **[S3]** Clients MUST show the `charge` value to the user before payment approval.
|
|
236
|
+
|
|
237
|
+
### 3.3 Schemas
|
|
238
|
+
|
|
239
|
+
- **[S4]** The roots of `inputSchema` and `outputSchema` MUST be `"type": "object"`. MCP tools require it for
|
|
240
|
+
both, and LLM function calling for the input.
|
|
241
|
+
- **[S5]** Schemas MUST NOT reference anything outside themselves: `$ref` and `$dynamicRef` values start with `#`,
|
|
242
|
+
and `$schema`, if present, is `https://json-schema.org/draft/2020-12/schema`. Clients MUST NOT fetch remote
|
|
243
|
+
schemas. The check is deliberately simple and also applies inside `const`, `default`, `enum` and `examples`.
|
|
244
|
+
- **[S6]** Each schema MUST be at most 32 KiB, measured as UTF-8 canonical JSON (RFC 8785).
|
|
245
|
+
- **[S7]** Providers SHOULD use the portable subset of JSON Schema, which most agent frameworks support:
|
|
246
|
+
- `type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, `const`
|
|
247
|
+
- `minimum`, `maximum`, `minLength`, `maxLength`, `minItems`, `maxItems`, `pattern`, `format`
|
|
248
|
+
- `title`, `description`, `default`, `examples`
|
|
249
|
+
- `anyOf`, plus `$defs` with local `$ref`
|
|
250
|
+
|
|
251
|
+
Providers SHOULD avoid `if`/`then`/`else`, `patternProperties` and `oneOf` or `allOf` at the root.
|
|
252
|
+
|
|
253
|
+
**Files and media:**
|
|
254
|
+
- Large inputs and outputs (images, audio, PDFs) SHOULD be passed as HTTPS URLs, using `"format": "uri"`.
|
|
255
|
+
- Small binaries MAY be inline, as `"contentEncoding": "base64"` with `"contentMediaType"`.
|
|
256
|
+
- **[S8]** URLs inside an output MUST remain valid for `jobs.retentionSeconds`.
|
|
257
|
+
|
|
258
|
+
### 3.4 Descriptions
|
|
259
|
+
|
|
260
|
+
An agent usually decides whether to call a service from its `description` alone. A good description says:
|
|
261
|
+
1. what the service does
|
|
262
|
+
2. when to use it
|
|
263
|
+
3. its limits: input size, languages, quality, or what it won't do
|
|
264
|
+
|
|
265
|
+
Don't put prices, URLs or instructions to the agent in the description. Price and network come from structured
|
|
266
|
+
fields only.
|
|
267
|
+
|
|
268
|
+
## 4. Payment
|
|
269
|
+
|
|
270
|
+
### 4.1 Payment options
|
|
271
|
+
|
|
272
|
+
`payment.accepts` uses the field names of an x402 v2 `PaymentRequirements` object, without `amount`, which comes
|
|
273
|
+
from each service's `price`.
|
|
274
|
+
|
|
275
|
+
| Field | Rules |
|
|
276
|
+
| --- | --- |
|
|
277
|
+
| `scheme` | `exact` |
|
|
278
|
+
| `network` | `stellar:testnet` or `stellar:pubnet` (CAIP-2) |
|
|
279
|
+
| `asset` | The USDC Stellar Asset Contract (`C…`) for that network |
|
|
280
|
+
| `payTo` | Stellar account (`G…`) or contract (`C…`) address that receives the payment. Muxed (`M…`) addresses are not allowed. |
|
|
281
|
+
| `maxTimeoutSeconds` | 30–300 |
|
|
282
|
+
| `extra.areFeesSponsored` | `true`. The facilitator pays network fees, so the payer needs only USDC. |
|
|
283
|
+
|
|
284
|
+
- **[P1]** In v1, `accepts` MUST have exactly one entry, so one manifest uses one network and one asset. The entry
|
|
285
|
+
MUST NOT include `amount`.
|
|
286
|
+
- **[P2]** A `G…` `payTo` MUST have a USDC trustline.
|
|
287
|
+
- **[P11]** `payTo` MUST be a valid strkey, with the right version byte and checksum.
|
|
288
|
+
|
|
289
|
+
### 4.2 The challenge
|
|
290
|
+
|
|
291
|
+
- **[P3]** create-job without payment returns `402` with the x402 v2 `PAYMENT-REQUIRED` header.
|
|
292
|
+
- **[P4]** The challenge's `accepts` MUST have exactly one entry. That entry MUST equal the manifest's entry, with
|
|
293
|
+
`amount` set to the service's `price.amount`. This applies to `scheme`, `network`, `asset`, `payTo`, `amount`,
|
|
294
|
+
`maxTimeoutSeconds` and `extra.areFeesSponsored`.
|
|
295
|
+
- **[P5]** `resource.url` MUST be the public HTTPS create-job URL, never an internal or `http://` URL.
|
|
296
|
+
- **[P6]** The challenge SHOULD include x402's Bazaar discovery extension (`extensions.bazaar`), built from the
|
|
297
|
+
service's `inputSchema`, `outputSchema` and first example. Generic x402 clients and catalogs can then use the
|
|
298
|
+
service.
|
|
299
|
+
- **[P7]** Clients MUST compare the challenge with the manifest the user approved. They MUST refuse to sign if any
|
|
300
|
+
field in [P4] differs.
|
|
301
|
+
|
|
302
|
+
### 4.3 Before payment, and after
|
|
303
|
+
|
|
304
|
+
- **[P8]** The provider MUST validate the input and check its capacity *before* sending a `402`. Nobody pays for
|
|
305
|
+
invalid input or a full queue (§5.8).
|
|
306
|
+
- **[P9]** One signed payment pays for at most one job. The provider MUST refuse to settle a payment it has
|
|
307
|
+
already used. It answers `402` with the x402 `error` set to `payment_already_used`.
|
|
308
|
+
- **[P10]** A response that completes settlement SHOULD carry the x402 `PAYMENT-RESPONSE` header. The job view
|
|
309
|
+
always carries the receipt (§5.6), including for jobs settled after the response was sent.
|
|
310
|
+
|
|
311
|
+
## 5. Jobs
|
|
312
|
+
|
|
313
|
+
### 5.1 Endpoints
|
|
314
|
+
|
|
315
|
+
All paths are relative to `baseUrl`.
|
|
316
|
+
|
|
317
|
+
| Operation | Request | Auth |
|
|
318
|
+
| --- | --- | --- |
|
|
319
|
+
| create-job | `POST /services/{name}/jobs` | x402 payment |
|
|
320
|
+
| check-status | `GET /jobs/{jobId}` | `Job-Token` |
|
|
321
|
+
| get-result | `GET /jobs/{jobId}/result` | `Job-Token` |
|
|
322
|
+
|
|
323
|
+
The paths are fixed. A client that knows `baseUrl` and a job id can always reach the job, even if it lost every
|
|
324
|
+
response.
|
|
325
|
+
|
|
326
|
+
- **[J17]** Providers SHOULD allow cross-origin requests to the manifest and all three endpoints, so browser
|
|
327
|
+
clients can call them directly. Responses carry `Access-Control-Allow-Origin: *` and
|
|
328
|
+
`Access-Control-Expose-Headers: PAYMENT-REQUIRED, PAYMENT-RESPONSE, Location, Retry-After`. An `OPTIONS`
|
|
329
|
+
preflight allows `GET` and `POST` and the headers `Content-Type`, `Idempotency-Key`, `Job-Token` and
|
|
330
|
+
`PAYMENT-SIGNATURE`. Without this, browser clients can reach the provider only through a server.
|
|
331
|
+
|
|
332
|
+
### 5.2 create-job request
|
|
333
|
+
|
|
334
|
+
- The body is the input object itself, as `Content-Type: application/json`.
|
|
335
|
+
- `Idempotency-Key`: a UUID (RFC 9562), lowercase. It becomes the job id. Clients SHOULD use UUID v4 or v7.
|
|
336
|
+
- `Job-Token`: 32 random bytes, base64url without padding (43 characters). Only the client and the provider
|
|
337
|
+
know it.
|
|
338
|
+
- `PAYMENT-SIGNATURE`: the x402 payment, on the paid request.
|
|
339
|
+
|
|
340
|
+
- **[J1]** Clients SHOULD send `Idempotency-Key` and `Job-Token`. They MUST send both or neither. A client that
|
|
341
|
+
sends them and saves them before paying can always recover the job.
|
|
342
|
+
- **[J2]** If both are missing, the provider creates the job id (a lowercase UUID) and token itself and returns
|
|
343
|
+
them once, in the create-job response (`jobId`, `jobToken`). This lets any x402 client pay. Such a client can still lose the job
|
|
344
|
+
if the response is lost.
|
|
345
|
+
- **[J3]** The provider stores only `sha256(Job-Token)` and compares it in constant time.
|
|
346
|
+
|
|
347
|
+
### 5.3 Binding
|
|
348
|
+
|
|
349
|
+
The **binding hash** of a request is:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
bindingHash = hex(sha256(JCS({ "service": <name>, "input": <input> })))
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
- **[J4]** A job id is bound to one binding hash and one token hash, permanently.
|
|
356
|
+
- **[J5]** create-job with an existing job id, the same binding hash and the same token returns the existing job.
|
|
357
|
+
There is no new `402`. If the request carries a payment, the provider MUST NOT settle it.
|
|
358
|
+
- **[J6]** The same job id with a different binding hash or token returns `422 idempotency_key_reused`.
|
|
359
|
+
- **[J7]** A request whose job id is still being claimed, verified or settled returns `409 request_in_progress`
|
|
360
|
+
with `Retry-After`.
|
|
361
|
+
|
|
362
|
+
### 5.4 Processing order
|
|
363
|
+
|
|
364
|
+
The provider processes a create-job request in this order:
|
|
365
|
+
|
|
366
|
+
1. Check the headers and the body size: `400` or `413`.
|
|
367
|
+
2. Find the service: `404 service_not_found`.
|
|
368
|
+
3. If the job id exists, apply [J5]–[J7].
|
|
369
|
+
4. Validate the input against `inputSchema`: `400 invalid_input`.
|
|
370
|
+
5. Check capacity: `429` or `503` with `Retry-After` (§5.8).
|
|
371
|
+
6. If there is no payment, answer `402` (§4.2).
|
|
372
|
+
7. Check and verify the payment through the facilitator. If it is invalid, answer `402` with the reason.
|
|
373
|
+
8. Claim the job id and the payment together, atomically ([J4], [P9]).
|
|
374
|
+
9. Settle and run the work in the order set by `charge` (§3.2).
|
|
375
|
+
10. Respond (§5.5).
|
|
376
|
+
|
|
377
|
+
### 5.5 Responses
|
|
378
|
+
|
|
379
|
+
create-job, check-status and get-result all return a **job view**:
|
|
380
|
+
|
|
381
|
+
```json
|
|
382
|
+
{
|
|
383
|
+
"jobId": "0192d3a4-7c1e-7b2a-9f00-3c5e8a1b2c3d",
|
|
384
|
+
"service": "text-stats",
|
|
385
|
+
"status": "succeeded",
|
|
386
|
+
"charge": "on-success",
|
|
387
|
+
"charged": true,
|
|
388
|
+
"createdAt": "2026-10-20T10:00:00Z",
|
|
389
|
+
"updatedAt": "2026-10-20T10:00:02Z",
|
|
390
|
+
"expiresAt": "2026-10-27T10:00:02Z",
|
|
391
|
+
"output": { "words": 2, "sentences": 1, "characters": 12 },
|
|
392
|
+
"payment": {
|
|
393
|
+
"network": "stellar:testnet",
|
|
394
|
+
"asset": "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
|
|
395
|
+
"amount": "100000",
|
|
396
|
+
"payTo": "GBZSBASBFMV5RGKPRIPCB5QQVAJ35IXKMTF2A2766MOA4ZHQ7MLSIPXU",
|
|
397
|
+
"payer": "GBH3JLK366N4L5XCLJRCYLE6K6K3PUWBBEOLNBJMLORSKUNR5WY2IRKZ",
|
|
398
|
+
"transaction": "3b1f…e9a0",
|
|
399
|
+
"settledAt": "2026-10-20T10:00:02Z"
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
| Field | Present |
|
|
405
|
+
| --- | --- |
|
|
406
|
+
| `output` | create-job and get-result, when `status` is `succeeded` |
|
|
407
|
+
| `error` | when `status` is `failed`: a problem object (§6) |
|
|
408
|
+
| `payment` | when `charged` is `true` |
|
|
409
|
+
| `queue` | when `status` is `queued`: the job's place in the queue (§5.8) |
|
|
410
|
+
| `jobToken` | only in the create-job response, and only when the provider created the token ([J2]) |
|
|
411
|
+
|
|
412
|
+
- **[J8]** If the job has finished (`succeeded` or `failed`), the response is `200`. Otherwise it is `202` with
|
|
413
|
+
`Retry-After` and, for create-job, `Location: {baseUrl}/jobs/{jobId}`.
|
|
414
|
+
- **[J9]** A provider MAY hold the create-job response open while the work runs, for at most 30 seconds. After
|
|
415
|
+
that it MUST answer `202`. Clients SHOULD use an HTTP timeout above 30 seconds.
|
|
416
|
+
- **[J10]** check-status and get-result without a valid `Job-Token`, for an unknown job, or for an expired job
|
|
417
|
+
return `404 job_not_found`. The response never reveals whether the job exists.
|
|
418
|
+
- **[J11]** check-status and get-result never take a payment and never cause a charge.
|
|
419
|
+
|
|
420
|
+
### 5.6 States
|
|
421
|
+
|
|
422
|
+
| `status` | Meaning | `charged` |
|
|
423
|
+
| --- | --- | --- |
|
|
424
|
+
| `queued` | Paid, or payment verified, and waiting to run | `true` for `upfront`; `false` for `on-success` |
|
|
425
|
+
| `running` | The work is in progress | as above |
|
|
426
|
+
| `succeeded` | Output ready | `true` |
|
|
427
|
+
| `failed` | Finished without output; see `error.code` | `true` for `upfront`, unless `error.code` is `payment_failed`; `false` for `on-success` |
|
|
428
|
+
| `payment_uncertain` | Settlement was attempted and its outcome is unknown | `"unknown"` |
|
|
429
|
+
|
|
430
|
+
Allowed transitions:
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
(new) → queued | running | payment_uncertain | failed
|
|
434
|
+
queued → running
|
|
435
|
+
running → succeeded | failed | payment_uncertain (payment_uncertain: on-success only)
|
|
436
|
+
payment_uncertain → queued | succeeded | failed (decided from on-chain data)
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
- **[J12]** `succeeded` and `failed` are final. To try again after a failure, the client uses a new job id.
|
|
440
|
+
- **[J13]** An uncertain settlement MUST NOT be retried with a new charge. The provider decides it from on-chain
|
|
441
|
+
data: the transaction, or a USDC `transfer` to `payTo`. If it was not settled, the job becomes `failed` with
|
|
442
|
+
`charged: false`.
|
|
443
|
+
- **[J14]** While `payment_uncertain`, the output is withheld.
|
|
444
|
+
- **[J15]** Jobs stay retrievable for `jobs.retentionSeconds` after they finish.
|
|
445
|
+
|
|
446
|
+
### 5.7 Receipt
|
|
447
|
+
|
|
448
|
+
The `payment` object uses x402 `SettleResponse` names where they exist.
|
|
449
|
+
|
|
450
|
+
| Field | Meaning |
|
|
451
|
+
| --- | --- |
|
|
452
|
+
| `network`, `asset`, `amount`, `payTo` | What was paid. They match the challenge. |
|
|
453
|
+
| `payer` | The paying address |
|
|
454
|
+
| `transaction` | Stellar transaction hash |
|
|
455
|
+
| `settledAt` | Time the provider saw the settlement |
|
|
456
|
+
|
|
457
|
+
- **[J16]** A receipt is a claim. Clients MUST confirm the transaction on chain before showing "paid": it
|
|
458
|
+
succeeded, and its `transfer` event moved the approved `asset`, from the payer to `payTo`, for the approved
|
|
459
|
+
`amount`. Clients compare with what they approved, not with the receipt, and accept one transaction as proof
|
|
460
|
+
for one job only.
|
|
461
|
+
|
|
462
|
+
### 5.8 Capacity and queuing
|
|
463
|
+
|
|
464
|
+
Every provider has limits: GPUs, worker processes, an upstream API's quota. When more requests arrive than it can
|
|
465
|
+
run at once, the provider queues them, or turns them away **before payment**. A user never pays for a job the
|
|
466
|
+
provider can't run, and load is never answered with a `500`, a dropped connection or a timeout.
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
create-job ──▶ step 5: a free slot? ──yes──▶ 402 → pay → running
|
|
470
|
+
│ no
|
|
471
|
+
▼
|
|
472
|
+
room in the queue? ──yes──▶ 402 → pay → queued (position, estimatedStartAt) → running
|
|
473
|
+
│ no
|
|
474
|
+
▼
|
|
475
|
+
503 unavailable + Retry-After nothing signed, nothing charged
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**Turning requests away**
|
|
479
|
+
|
|
480
|
+
- **[Q1]** A provider that can't take a new job SHOULD answer create-job with `503 unavailable` when the service is
|
|
481
|
+
at capacity, or `429 rate_limited` when this caller sent too many requests. Both carry `Retry-After` in whole
|
|
482
|
+
seconds. They are answered at step 5 of §5.4, before the payment is asked for or verified, so nothing is signed
|
|
483
|
+
or charged ([P8]).
|
|
484
|
+
- **[Q2]** A provider SHOULD take a job only if it expects the job to start within the
|
|
485
|
+
`capacity.maxQueueWaitSeconds` it advertises. For `charge: "on-success"` it SHOULD also expect the work and the
|
|
486
|
+
settlement to finish before the signed payment expires ([S2]). Otherwise it answers `503`.
|
|
487
|
+
- **[Q3]** A provider SHOULD check capacity again on the paid request, because the queue may have filled since the
|
|
488
|
+
unpaid request got its `402`. Like every `429` or `503` from create-job, that answer comes before the payment is
|
|
489
|
+
claimed or settled (§5.4), so nothing is charged.
|
|
490
|
+
|
|
491
|
+
**Queued jobs**
|
|
492
|
+
|
|
493
|
+
- **[Q4]** Once a job is accepted, load SHOULD NOT fail it: it stays `queued` until a slot is free. An `upfront`
|
|
494
|
+
job is already paid, so its place in the queue SHOULD survive a restart of the provider. An `on-success` job
|
|
495
|
+
that can't start before its payment expires fails with `timeout` and is not charged.
|
|
496
|
+
- **[Q5]** While a job is `queued`, its job view SHOULD include `queue`:
|
|
497
|
+
|
|
498
|
+
```json
|
|
499
|
+
"queue": { "position": 3, "estimatedStartAt": "2026-10-20T10:00:12Z" }
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
`position` is 1 for the next job to start. `estimatedStartAt` (RFC 3339) is a hint, not a promise.
|
|
503
|
+
- **[Q6]** The `Retry-After` of a `202` SHOULD say when the job is likely to have changed, from 1 to 60 seconds.
|
|
504
|
+
For a queued job, that is about its `estimatedStartAt`.
|
|
505
|
+
- **[Q7]** Capacity limits apply to create-job. A provider at capacity SHOULD keep answering check-status and
|
|
506
|
+
get-result, so paid jobs can always be collected.
|
|
507
|
+
|
|
508
|
+
**Clients**
|
|
509
|
+
|
|
510
|
+
- **[Q8]** On a `429` or `503` from create-job, a client SHOULD wait `Retry-After`, plus a little random jitter,
|
|
511
|
+
and send the same request again: the same `Idempotency-Key`, `Job-Token` and input and, on a paid request, the
|
|
512
|
+
same `PAYMENT-SIGNATURE`. One payment still buys at most one job ([J5], [P9]), so this is safe. The client stops
|
|
513
|
+
before the signed payment expires, gives up after a bounded wait, and tells the user the provider is busy and
|
|
514
|
+
nothing was charged.
|
|
515
|
+
- **[Q9]** A client SHOULD poll check-status and get-result no faster than `Retry-After` says ([Q6]). A `429` or
|
|
516
|
+
`503` while polling only means "ask again later".
|
|
517
|
+
|
|
518
|
+
**Advertising capacity**
|
|
519
|
+
|
|
520
|
+
Each service MAY describe its capacity in the manifest:
|
|
521
|
+
|
|
522
|
+
```json
|
|
523
|
+
"capacity": { "concurrency": 4, "maxQueueWaitSeconds": 60 }
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
| Field | Type | Rules |
|
|
527
|
+
| --- | --- | --- |
|
|
528
|
+
| `capacity.concurrency` | integer | Jobs the service runs at the same time. At least 1. |
|
|
529
|
+
| `capacity.maxQueueWaitSeconds` | integer | Longest a new job may expect to wait before create-job answers `503` instead. 0 means no queue. |
|
|
530
|
+
|
|
531
|
+
Both are hints: discovery can show them, and a client can pick a less busy provider. The provider's answers
|
|
532
|
+
([Q1], [Q5]) are what count. How a provider scales behind them (worker processes, a shared queue, autoscaling) is
|
|
533
|
+
up to it; the standard only fixes what clients see.
|
|
534
|
+
|
|
535
|
+
## 6. Errors
|
|
536
|
+
|
|
537
|
+
Errors other than `402` use RFC 9457 problem details (`Content-Type: application/problem+json`):
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{
|
|
541
|
+
"type": "<spec URL>#error-invalid_input",
|
|
542
|
+
"title": "Input does not match the service's inputSchema",
|
|
543
|
+
"status": 400,
|
|
544
|
+
"code": "invalid_input",
|
|
545
|
+
"errors": [{ "pointer": "/text", "message": "must be a string" }]
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
- **[E1]** Every problem has a `code`. Clients MUST use `code`, not `title` or `detail`, to decide what to do.
|
|
550
|
+
- **[E2]** Problems never contain request headers, payment payloads or job tokens.
|
|
551
|
+
- `402` responses use x402's own format.
|
|
552
|
+
|
|
553
|
+
| `code` | HTTP | When |
|
|
554
|
+
| --- | --- | --- |
|
|
555
|
+
| `invalid_request` | 400 | Malformed JSON, wrong content type, bad header format, or only one of `Idempotency-Key` / `Job-Token` |
|
|
556
|
+
| `invalid_input` | 400 | The input fails `inputSchema`. `errors[]` lists JSON Pointers. |
|
|
557
|
+
| `service_not_found` | 404 | No service with that name |
|
|
558
|
+
| `job_not_found` | 404 | Unknown, expired, or wrong or missing token |
|
|
559
|
+
| `request_in_progress` | 409 | [J7]; with `Retry-After` |
|
|
560
|
+
| `payload_too_large` | 413 | Body over `maxInputBytes` |
|
|
561
|
+
| `idempotency_key_reused` | 422 | [J6] |
|
|
562
|
+
| `rate_limited` | 429 | This caller sent too many requests (§5.8); with `Retry-After` |
|
|
563
|
+
| `unavailable` | 503 | The service is at capacity (§5.8), or a dependency such as the facilitator is down; with `Retry-After` |
|
|
564
|
+
|
|
565
|
+
Codes for a failed job, in the job view's `error.code`:
|
|
566
|
+
|
|
567
|
+
| `code` | Meaning |
|
|
568
|
+
| --- | --- |
|
|
569
|
+
| `service_failed` | The work failed |
|
|
570
|
+
| `output_invalid` | The output did not match `outputSchema` |
|
|
571
|
+
| `timeout` | The work did not finish in time |
|
|
572
|
+
| `payment_failed` | Settlement did not happen, so the job did not run or its output is withheld. Not charged. |
|
|
573
|
+
|
|
574
|
+
## 7. Stellar 8004 identity
|
|
575
|
+
|
|
576
|
+
- **[I1]** The provider's Stellar 8004 registration file MUST list the manifest in `services`:
|
|
577
|
+
`{ "name": "stellar-agent-services", "endpoint": "<manifest URL>", "version": "1" }`.
|
|
578
|
+
It SHOULD also list `{ "name": "web", "endpoint": "<baseUrl>" }` and set the `x402` flag.
|
|
579
|
+
- **[I2]** The manifest's `provider.agentId` MUST be the id of that registration.
|
|
580
|
+
- **[I3]** The manifest's `payTo` MUST equal the agent's on-chain wallet, `get_agent_wallet(agentId)`.
|
|
581
|
+
|
|
582
|
+
Together, these three rules authenticate the manifest without signing it:
|
|
583
|
+
- the on-chain registration names the manifest URL
|
|
584
|
+
- HTTPS proves the origin
|
|
585
|
+
- the wallet the user pays is the one the owner set on chain
|
|
586
|
+
|
|
587
|
+
The provider runtime needs no secret key.
|
|
588
|
+
|
|
589
|
+
Verification results are published to the Stellar 8004 Validation Registry. See
|
|
590
|
+
[verification.md](verification.md) and, for the shared conventions with ERC-8126,
|
|
591
|
+
[erc-compatibility.md](erc-compatibility.md).
|
|
592
|
+
|
|
593
|
+
## 8. Conformance levels
|
|
594
|
+
|
|
595
|
+
| Level | Name | Checks | Badge |
|
|
596
|
+
| --- | --- | --- | --- |
|
|
597
|
+
| 1 | Described | C1 manifest, C6 hygiene | Standard v1 |
|
|
598
|
+
| 2 | Payable | Level 1 + C3 challenge, C4 paid call | Payment verified |
|
|
599
|
+
| 3 | Recoverable | Level 2 + C5 recovery | Recovery verified |
|
|
600
|
+
| – | Registered | C2 identity, plus a Validation Registry record | 8004 validated |
|
|
601
|
+
| – | Load-safe | C7 load: a burst of requests is answered with `402`, `429` or `503`, never an error (§5.8) | Load-safe |
|
|
602
|
+
|
|
603
|
+
- Each level includes the one below. Registered and Load-safe are independent of the levels.
|
|
604
|
+
- `verifier check <manifest-url>` runs every check that needs no payment: C1, C2, C3, C6 and C7's burst. In
|
|
605
|
+
development mode it also accepts a local `http://` server ([M6]).
|
|
606
|
+
- `verifier verify <manifest-url> --pay` runs every check with a funded testnet payer.
|
|
607
|
+
|
|
608
|
+
The checks are listed in [verification.md](verification.md).
|
|
609
|
+
|
|
610
|
+
- **[V1]** Clients SHOULD enforce any verification requirements configured by their owner before signing. Evidence
|
|
611
|
+
should come from a trusted source, match the approved manifest, payee and network, and be within that source's
|
|
612
|
+
validity window. Missing, unreadable or unverifiable evidence should block a payment that requires it.
|
|
613
|
+
|
|
614
|
+
## 9. Versioning and extensions
|
|
615
|
+
|
|
616
|
+
- Changes inside v1 only add optional fields. `standard` stays `stellar-agent-services/provider@1`.
|
|
617
|
+
- A breaking change gets a new major version (`@2`).
|
|
618
|
+
- **[X1]** `extensions` objects (top level and per service) hold fields outside this standard. Keys SHOULD be
|
|
619
|
+
reverse-DNS names, such as `com.example.sla`. Clients ignore extensions they don't know.
|
|
620
|
+
|
|
621
|
+
## 10. Security considerations
|
|
622
|
+
|
|
623
|
+
- **Keys:** the provider runtime holds no Stellar secret key. It knows its `payTo` address and a facilitator
|
|
624
|
+
URL. Owner actions such as registration run offline.
|
|
625
|
+
- **Job tokens:** they are bearer secrets. Providers store only their hash, and `404` hides whether a job exists.
|
|
626
|
+
- **Cross-origin access:** `Access-Control-Allow-Origin: *` ([J17]) is safe because no request uses cookies or
|
|
627
|
+
ambient credentials. Access to a job needs its `Job-Token` header, and paying needs a signed payment.
|
|
628
|
+
- **Manifest changes:** a provider can change its manifest at any time. Clients approve a specific manifest and
|
|
629
|
+
check the challenge against it ([P7]).
|
|
630
|
+
- **Schemas:** no remote `$ref` ([S5]), and size limits on every field.
|
|
631
|
+
- **Prompt injection:** `description`, `title` and examples are untrusted text. Clients that show them to an LLM
|
|
632
|
+
SHOULD label them as provider-supplied.
|
|
633
|
+
|
|
634
|
+
## 11. Not in v1
|
|
635
|
+
|
|
636
|
+
Each item has room to be added later without breaking v1.
|
|
637
|
+
|
|
638
|
+
| Feature | Where it would go |
|
|
639
|
+
| --- | --- |
|
|
640
|
+
| Usage-based pricing, such as per token | `price` gains new fields; x402 is adding an `upto` scheme |
|
|
641
|
+
| Several networks or assets | `payment.accepts` already is an array |
|
|
642
|
+
| Other payment protocols, such as MPP | `payment.protocol` |
|
|
643
|
+
| Streaming output | A new optional endpoint |
|
|
644
|
+
| Webhooks | An optional callback field on create-job |
|
|
645
|
+
| Priority or reserved capacity | `capacity` gains fields; a price tier could buy an earlier slot |
|
|
646
|
+
| Refunds | A new `charge` value |
|
|
647
|
+
|
|
648
|
+
## Appendix A: example exchange
|
|
649
|
+
|
|
650
|
+
Unpaid request:
|
|
651
|
+
|
|
652
|
+
```http
|
|
653
|
+
POST /agent/services/text-stats/jobs HTTP/1.1
|
|
654
|
+
Host: api.example.com
|
|
655
|
+
Content-Type: application/json
|
|
656
|
+
Idempotency-Key: 0192d3a4-7c1e-7b2a-9f00-3c5e8a1b2c3d
|
|
657
|
+
Job-Token: q7Zt3m0bX0s8b6f5JmS2kQ9xg3Y1rZcV4nL8pW2eA0c
|
|
658
|
+
|
|
659
|
+
{"text":"Hello world."}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
```http
|
|
663
|
+
HTTP/1.1 402 Payment Required
|
|
664
|
+
PAYMENT-REQUIRED: <base64 x402 PaymentRequired: accepts = manifest entry + amount "100000">
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Paid retry, with the same headers and body plus `PAYMENT-SIGNATURE`:
|
|
668
|
+
|
|
669
|
+
```http
|
|
670
|
+
HTTP/1.1 200 OK
|
|
671
|
+
Content-Type: application/json
|
|
672
|
+
PAYMENT-RESPONSE: <base64 x402 SettleResponse>
|
|
673
|
+
|
|
674
|
+
{ "jobId": "0192d3a4-…", "status": "succeeded", "charged": true, "output": { … }, "payment": { … } }
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
If that response is lost, the client asks again without paying:
|
|
678
|
+
|
|
679
|
+
```http
|
|
680
|
+
GET /agent/jobs/0192d3a4-7c1e-7b2a-9f00-3c5e8a1b2c3d/result HTTP/1.1
|
|
681
|
+
Host: api.example.com
|
|
682
|
+
Job-Token: q7Zt3m0bX0s8b6f5JmS2kQ9xg3Y1rZcV4nL8pW2eA0c
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
A longer `upfront` job answers `202 Accepted` with `Location` and `Retry-After`. The client then polls
|
|
686
|
+
check-status until the job finishes and calls get-result.
|
|
687
|
+
|
|
688
|
+
A busy service queues the job and says where it stands:
|
|
689
|
+
|
|
690
|
+
```http
|
|
691
|
+
HTTP/1.1 202 Accepted
|
|
692
|
+
Content-Type: application/json
|
|
693
|
+
Location: https://api.example.com/agent/jobs/0192d3a4-7c1e-7b2a-9f00-3c5e8a1b2c3d
|
|
694
|
+
Retry-After: 12
|
|
695
|
+
|
|
696
|
+
{ "jobId": "0192d3a4-…", "status": "queued", "charged": true, "queue": { "position": 3, "estimatedStartAt": "2026-10-20T10:00:12Z" }, … }
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
A full one answers before payment, and the client sends the same request again later:
|
|
700
|
+
|
|
701
|
+
```http
|
|
702
|
+
HTTP/1.1 503 Service Unavailable
|
|
703
|
+
Content-Type: application/problem+json
|
|
704
|
+
Retry-After: 20
|
|
705
|
+
|
|
706
|
+
{ "type": "…#error-unavailable", "title": "The service is unavailable", "status": 503, "code": "unavailable", "detail": ""text-stats" is at capacity; nothing was charged" }
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
## Appendix B: mapping a service to an MCP tool
|
|
710
|
+
|
|
711
|
+
A client can offer each service to an LLM as a tool without converting anything:
|
|
712
|
+
|
|
713
|
+
| MCP tool | Service |
|
|
714
|
+
| --- | --- |
|
|
715
|
+
| `name` | `name` |
|
|
716
|
+
| `title` | `title` |
|
|
717
|
+
| `description` | `description` |
|
|
718
|
+
| `inputSchema` | `inputSchema` |
|
|
719
|
+
| `outputSchema` | `outputSchema` |
|
|
720
|
+
|
|
721
|
+
Both schemas have an object root ([S4]), as MCP requires. The price, network and `charge` stay outside the tool
|
|
722
|
+
definition. The client shows them to the user for approval.
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
The manifest JSON Schema is [`packages/protocol/schemas/manifest.schema.json`](../packages/protocol/schemas/manifest.schema.json).
|
|
727
|
+
Test vectors for canonical JSON, binding hashes, header formats, manifests and challenges are in
|
|
728
|
+
[`packages/protocol/test-vectors`](../packages/protocol/test-vectors).
|