Rails
Peer protocol

A2A

A peer agent discovers AbaPay via its Agent Card, then sends structured tool calls over A2A JSON-RPC — no browser, no human account-creation step, for either side.

JSON-RPC endpoint
agents.abapays.com/api/a2a

Skills (11) — full reference

describe_capabilitiesRead-only

List what AbaPay can pay (airtime, data, electricity, cable, etc.), any services currently paused, and example requests. Call this first if unsure what is supported.

No parameters.

check_balanceRead-only

Check a linked wallet's stablecoin balances and remaining agent spending allowance. Works with no arguments once authorized via OAuth.

NameTypeRequiredDescription
api_keystringOptionalAbaPay MCP API key (starts with aba_mcp_). Not needed over OAuth.
chain"CELO"OptionalWhich chain to check.Backend also accepts BASE for the multi-chain consumer app; this site's rails are Celo-only.
list_plansRead-only

List the REAL, currently purchasable plans for DATA, CABLE, or EDUCATION — exact codes and current prices. Always call before pay_bill for these three services.

NameTypeRequiredDescription
service"DATA" | "CABLE" | "EDUCATION"RequiredWhich service to list plans for.
providerstringRequirede.g. mtn, airtel, glo, 9mobile (data); dstv, gotv, startimes (cable); waec, waec-registration, jamb (education).
list_international_optionsRead-only

Browse the live international top-up catalogue (140+ countries) one level at a time: country → product type → operator → priced plan.

NameTypeRequiredDescription
countrystringOptionalCountry name or ISO code, e.g. "Ghana" or "GH". Omit to list all countries.
product_type_idstringOptionalFrom this country's results. Omit to list product types.
operator_idstringOptionalFrom this country + product_type_id's results. Omit to list operators.
transaction_historyRead-only

List recent real transactions for the linked wallet — service, provider, amount, status, tx hash. No PIN required.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
limitnumberOptionalHow many to return. Defaults to 10, max 25.
offsetnumberOptionalHow many of the most recent to skip. 0 (default) starts at the newest.
get_payment_statusRead-only

Look up one payment by tx hash or request id: delivered, still confirming, failed or refunded, with refund state. Only the linked wallet's payments. No PIN required.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
referencestringRequiredThe transaction hash (0x…) or request id.
pay_billMoves money

Pay a real bill — Nigerian (airtime, data, electricity, cable, WAEC/JAMB) or international airtime/data — from the linked wallet, settled on-chain. Executes immediately; no delay parameter exists.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
pinstringRequiredThe key's PIN (6 digits for keys created now; older keys may have 4-6). Required on every payment, including over OAuth.
idempotency_keystringOptionalA unique id for this payment (8-128 chars). A retry with the same key returns the first result instead of paying again.
service"AIRTIME" | "DATA" | "ELECTRICITY" | "CABLE" | "EDUCATION" | "INTERNATIONAL"RequiredWhich kind of bill.
providerstringOptionale.g. mtn, ikeja-electric, dstv, waec. Not used for INTERNATIONAL.
account_numberstringRequiredPhone/meter/smartcard/JAMB ID/destination number, depending on service.
amount_ngnnumberOptionalAmount in Naira. Not needed for INTERNATIONAL.
chain"CELO"OptionalDefaults to the chain approved on the key.Backend also accepts BASE for the consumer app; Celo-only here.
token"USD₮" | "USDC" | "USA₮"OptionalWhich stablecoin to pay with. Defaults to the token approved on the key.
variation_codestringOptionalRequired for DATA, EDUCATION, INTERNATIONAL, and CABLE package changes.
meter_type"prepaid" | "postpaid"OptionalRequired for ELECTRICITY.
customer_emailstringOptionalRequired for INTERNATIONAL (receipt destination).
customer_namestringOptionalOptional, used for the receipt if known.
countrystringOptionalRequired for INTERNATIONAL.
product_type_idstringOptionalRequired for INTERNATIONAL.
operator_idstringOptionalRequired for INTERNATIONAL.
schedule_billWrite

Set up a recurring or future one-off bill payment. Charges nothing when this runs — money only moves later, when the schedule fires and the allowance still covers it. EDUCATION and INTERNATIONAL can't be scheduled.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
pinstringRequiredRequired to create a schedule.
idempotency_keystringOptionalA unique id for this payment (8-128 chars). A retry with the same key returns the first result instead of paying again.
service"AIRTIME" | "DATA" | "ELECTRICITY" | "CABLE"RequiredWhich kind of bill.
providerstringOptionale.g. mtn, ikeja-electric, dstv.
account_numberstringRequiredPhone/meter/smartcard number.
amount_ngnnumberRequiredAmount to charge each run.
variation_codestringOptionalRequired for DATA and CABLE package changes.
meter_type"prepaid" | "postpaid"OptionalRequired for ELECTRICITY.
frequency"daily" | "weekly" | "monthly" | "once"RequiredHow often this runs.
day_of_weeknumberOptionalRequired when frequency is "weekly" — 0 (Sun) to 6 (Sat).
day_of_monthnumberOptionalRequired when frequency is "monthly" — 1-28.
schedule_in_minutesnumberOptionalRequired when frequency is "once".
chain"CELO"OptionalDefaults to the key's approved chain.Celo-only here; backend also accepts BASE for the consumer app.
token"USD₮" | "USDC" | "USA₮"OptionalDefaults to the key's approved token.
customer_emailstringOptionalNotified when this runs — MCP has no persistent channel to message back.
list_schedulesRead-only

List active recurring/one-off bill schedules for the linked wallet. No PIN required. Returns each schedule's id for cancel_schedule.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
cancel_scheduleWrite

Cancel active schedules. Pass id for exactly one (no PIN). provider (all of that provider's) or all: true (every schedule) require the PIN. With none of these the call is refused.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
idstringOptionalExact schedule id from list_schedules. Cancels only that one.
providerstringOptionalCancel every active schedule for this provider, e.g. "mtn". Requires pin. Ignored if id is set.
allbooleanOptionaltrue cancels every active schedule on the wallet. Requires pin.
pinstringOptionalRequired with provider or all; not needed for a single id.
pay_bill_batchMoves money

Pay airtime or data to 2-20 recipients in one call, one PIN for the whole batch. All-or-nothing on capacity: if any (chain, token) group is short, nothing moves. Executes immediately.

NameTypeRequiredDescription
api_keystringOptionalNot needed over OAuth.
pinstringRequiredAuthorizes the whole batch.
idempotency_keystringOptionalA unique id for this payment (8-128 chars). A retry with the same key returns the first result instead of paying again.
recipientsarrayRequired2-20 objects, each: service (AIRTIME|DATA), provider, account_number, amount_ngn, variation_code (DATA only), chain/token overrides.
chain"CELO"OptionalDefault chain for recipients that don't set their own.
token"USD₮" | "USDC" | "USA₮"OptionalDefault token for recipients that don't set their own.
customer_emailstringOptionalOptional, applies to the whole batch.

Can an agent set and use a PIN without visiting a website? Yes.

The PIN is a field in a JSON body, not something entered into a web form. Linking and every payment after it are both plain API calls:

POST /api/agent/link — a call authenticated with a Sign-In with Ethereum (EIP-4361) signature, using a single-use nonce from GET /api/auth/nonce. It picks the PIN in that same request and mints an Agent Hub api_key back. No session, no browser.
Every pay_bill / pay_bill_batch / schedule_bill call after that sends the same PIN back as another JSON field — still just an HTTP request.
POST /api/agent/link — real fields, verified live 2026-09-11
headers: {
  "x-wallet-address": "0xYourAgentWallet...",
  "x-wallet-signature": "<signature over the SIWE message>",
  "x-wallet-siwe": "<the SIWE message, base64>"
}
body: {
  "wallet_address": "0xYourAgentWallet...",
  "channel": "MCP",
  "pin": "123456",
  "approved_chain": "CELO"
}
-> { "success": true, "api_key": "aba_mcp_..." }

Full runnable version: examples/agent-quickstart.mjs. Or skip the wire format entirely: AbaPayAgent.link() in the SDK does this in one line. CORS is open on this endpoint too — try it straight from a browser.

The PIN itself is still real security, not a formality: it's the one thing separate from the signature that has to be right on every payment call, unlike x402, which needs neither a PIN nor a link step at all. Two different trust models, both entirely API-driven — neither ever requires opening a browser.