Your Agent's Trial Call Needs a Signature — and the Service ID Isn't What You Think

August 19, 2026 · Iris, Growth @ minia2a.uk

An agent registers a self-custody wallet, gets its free credits, appends ?wallet=0x… to an endpoint the way every quickstart shows — and gets HTTP 402. Nothing is down. The call simply isn't signed, and an unsigned call never reaches the wallet's own trial bucket.

This post is the missing page: the exact message to sign, the one detail inside it that will cost you an afternoon if nobody tells you, and four recipes that work. Everything below was measured against the live gateway on August 19, 2026 — the pass/fail marks are real responses, not documentation.

Two buckets, not one

There are two independent pools of free calls:

They really are independent. The measurements in this post were taken from a host whose anonymous bucket was already at 0/15: unsigned calls returned 402, and signed calls from the same host, same second, returned 200. If your agents run behind shared cloud egress — Lambda, CI runners, a Workers deploy where thousands of tenants share an IP — this is the difference between "free tier exhausted before I started" and "my wallet carries its own allowance wherever it runs."

The message

Sign this string with EIP-191 (personal_sign), send the signature and the timestamp as headers, and put the wallet in the query string:

minia2a trial:<wallet>:<serviceId>:<unixSeconds>

The detail that costs an afternoon

You are standing at the URL https://minia2a.uk/x402/time. The obvious reading of serviceId is time. It isn't. It's the catalog idx402-time — the value in the id field of /api/services, also echoed in every 402 body at extensions.bazaar.routeTemplate.

Four candidates, same wallet, same endpoint, same minute:

Signed asResult
x402-time200x-trial-mode: wallet, x-trial-remaining: 14
time402
/x402/time402
https://minia2a.uk/x402/time402

Repeated on a second endpoint for confirmation: gas → 402, x402-gas → 200.

Here is why this is worse than an ordinary gotcha. A wrong service ID and a forged signature produce byte-identical responses. Both are a 402 with trial.mode:"none". An agent debugging this has no signal telling it whether its crypto is wrong, its clock is wrong, its registration failed, or it merely used the wrong noun — so it tends to go rewrite the signing code, which was fine all along. If you take one thing from this post: check the ID first.

A note for anyone designing a payment challenge

A 402 body that names a variable — "sign wallet:service:ts" — is not machine-readable. The client has to guess what service expands to, and every wrong guess looks exactly like an auth failure. Emit the literal string, or name the field it comes from. The cost of the ambiguity lands entirely on the caller, which in the agent economy means it lands on software that cannot file a bug report.

Four working recipes

1. curl + cast

W=0xYOUR_WALLET
TS=$(date +%s)
SIG=$(cast wallet sign --private-key $PK "minia2a trial:$W:x402-time:$TS")

curl "https://minia2a.uk/x402/time?wallet=$W" \
  -H "X-Wallet-Signature: $SIG" \
  -H "X-Trial-Timestamp: $TS"
# 200 · x-trial-mode: wallet · x-trial-remaining: 14

2. Python

import time, requests
from eth_account import Account
from eth_account.messages import encode_defunct

acct, sid = Account.from_key(PRIVATE_KEY), "x402-time"
ts = str(int(time.time()))
sig = acct.sign_message(
    encode_defunct(text=f"minia2a trial:{acct.address}:{sid}:{ts}")
).signature.hex()

r = requests.get(f"https://minia2a.uk/x402/time?wallet={acct.address}",
                 headers={"X-Wallet-Signature": sig, "X-Trial-Timestamp": ts})
print(r.status_code, r.headers.get("x-trial-mode"), r.headers.get("x-trial-remaining"))

3. CLI — it prints the string for you

The CLI resolves the catalog ID itself, so the ID trap can't reach you. Run it without a signature and it hands you the exact line to sign:

npx -y minia2a-skill call x402-time --wallet 0xYOUR_WALLET
# → sign this exact message (EIP-191 personal_sign):
#     minia2a trial:0xYOUR_WALLET:x402-time:1787113324

npx -y minia2a-skill call x402-time --wallet 0xYOUR_WALLET \
  --timestamp 1787113324 --signature 0xYOUR_SIGNATURE
# → trial: mode=wallet remaining=12

4. MCP — signing handled in-process

Inside Claude Code, Cursor, or any MCP client, pass a key once (argument or MINIA2A_PRIVATE_KEY) and the server signs each call locally — only the signature crosses the wire, and the service ID comes from the catalog automatically:

npx -y minia2a-mcp        # v1.1.21+
# minia2a_call_service { serviceId: "x402-time", privateKey: "0x..." }
# → { "trialMode": "wallet", "trialRemaining": "13", ... }

Reading the answer

Don't infer which bucket paid — the gateway tells you, on every successful call:

That ip case is the quiet one. A 200 doesn't prove your signing works; it may just prove your IP still had calls left. Test from an exhausted IP, or watch the header — those are the only two honest checks.

When both buckets are empty

You get a 402 carrying a standard x402 v2 accepts[] array: amount in micro-units, asset, network as CAIP-2, payTo, scheme. Those are real addresses on real chains — settle one and retry with a payment header. The free buckets are there so an agent can find out whether an endpoint is worth paying for before it spends anything, which is the whole point of a trial: not a discount, but a way for autonomous software to evaluate a supplier without a purchase order.

Full machine-readable spec: AGENTS.md · llms.txt · service catalog: /api/services