Connect an agent
One economic request. Three ways to ask.
An agent wants to commit €1,180.00 to a twelve-month observability plan, against cost centre CC-OPS-114. That is above the policy’s €250 approval threshold, so the answer is not yes. Below is that exact request through the MCP tool, over REST, and from the TypeScript SDK. The same Zod schema validates all three, the same engine decides them, and all three land in the same Activity record.
None of these can move money. A spend request is a question. The answer is recorded and returned; no supplier is contacted, no approver is asked and no rail is called. Nothing here executes a payment.
1 - MCP: observe_spend_request
For an agent that speaks the Model Context Protocol. The server exposes eight tools; this is the one that asks a policy question.
The MCP server acts for the agent its credential resolves to, and that resolution happens here rather than on the caller’s machine. Nothing local can state which agent it is: the tool takes no agent parameter, and there is no environment variable that names one.
→ tool: observe_spend_request
{
"recipientId": "rcp_01JQ8F7Z9K3M4N5P6Q7R8S9T0V",
"amount": "1180.00",
"category": "software",
"commitmentType": "PURCHASE_ORDER",
"purpose": "Observability plan, 12 months, renewal
for the platform team.",
"projectReference": "CC-OPS-114"
}← result
This would have been held for a person to
decide.
AMOUNT_REQUIRES_APPROVAL - €1,180.00 is at or above
the €250.00 threshold, so a person has to approve it.
Nothing was paid, ordered or reserved. This request
was observed, not authorized.
https://…/app/requests/spi_01JQ…2 - REST: POST /api/v1/spend-intents
The decision comes back in the same response. An agent that had to poll for the answer would have acted before it arrived.
POST /api/v1/spend-intents
Content-Type: application/json
X-Paygente-Demo: <token from POST /api/v1/demo/sessions>
Idempotency-Key: procurement-2026-04-02-0031
{
"agentId": "agt_01JQ8F7Z9K3M4N5P6Q7R8S9T0V",
"recipientId": "rcp_01JQ8F7Z9K3M4N5P6Q7R8S9T0V",
"asset": "EUR",
"amount": "1180.00",
"category": "software",
"commitmentType": "PURCHASE_ORDER",
"purpose": "Observability plan, 12 months, renewal for the platform team.",
"projectReference": "CC-OPS-114"
}201 Created
{
"intent": {
"id": "spi_01JQ8F7Z9K3M4N5P6Q7R8S9T0V",
"mode": "SHADOW",
"money": {
"asset": "EUR",
"amountMinor": "118000",
"amount": "1180.00",
"amountDisplay": "€1,180.00"
},
"category": "software",
"commitmentType": "PURCHASE_ORDER",
"projectReference": "CC-OPS-114"
},
"decision": {
"outcome": "REQUIRE_APPROVAL",
"explanation": "This would have been held for a
person to decide.",
"reasons": [
{ "code": "AMOUNT_REQUIRES_APPROVAL",
"message": "€1,180.00 is at or above the €250.00
threshold, so a person has to approve it." }
],
"engineVersion": "policy-engine@1.1.0"
},
"url": "https://…/app/requests/spi_01JQ…"
}3 - TypeScript SDK
Provide a stable idempotency key for each logical request and reuse that same key when retrying it. Paygente replays the original result for the same key and payload, and rejects reuse with a different payload. The client forwards the key you give it and never invents one - a value minted per attempt would be new on every retry, which protects nothing.
import { PaygenteClient } from '@paygente/sdk';
const paygente = new PaygenteClient({
baseUrl: process.env.PAYGENTE_API_URL!,
// Demo sessions only. Production authentication is not built yet.
demoToken: process.env.PAYGENTE_DEMO_TOKEN!,
});
const { decision, url } = await paygente.submitSpendIntent(
{
agentId,
recipientId,
asset: 'EUR',
amount: '1180.00',
category: 'software',
commitmentType: 'PURCHASE_ORDER',
purpose: 'Observability plan, 12 months, renewal for the platform team.',
projectReference: 'CC-OPS-114',
},
// Stable across retries of this same order — not a fresh value each attempt.
{ idempotencyKey: 'procurement-2026-04-02-0031' },
);
if (decision.outcome !== 'ALLOW') {
// Do not commit to it. Nothing was charged either way — Paygente
// records the answer, it does not act on it.
console.error(decision.explanation, url);
}Authentication, honestly
What works today
An agent key, for an agent. Issue one from your sandbox under Agents and send it as Authorization: Bearer. It resolves server-side to one organization and one agent, and nothing the caller sends can widen that.
An anonymous demo session, for a person. POST /api/v1/demo/sessions returns a token; send it as X-Paygente-Demo. It is shown once, scoped to one throwaway organization, and it expires.
What does not exist yet
Service accounts, scopes and production authentication are not built. An agent key lives as long as the sandbox that issued it and carries one fixed permission set; a demo session is not a production credential. Neither is an account, and neither should be treated as one. Do not put real supplier data in the sandbox.