Quickstart
By the end of this page you will have an HTTP endpoint that refuses unpaid requests with a USDC price attached, and publishes a manifest any agent can read.
Ripar is an SDK, a CLI and an MCP server. There is no hosting platform: nothing here builds, deploys or runs your code for you, and there is no account to create. You run the process; see Deploy anywhere for where to put it.
Node 20+, git, and an Algorand address to receive funds (Pera, Defly or any wallet).
1 · Install the SDK
@ripar/sdk is not on npm yet — npm install @ripar/sdk returns a 404. Install it from
the repository:
git clone https://github.com/nickthelegend/ripar-sdk && cd ripar-sdk
npm install && npm run build
npm link # puts the `ripar` command on your PATHThe ripar command ships inside the SDK package — there is no separate CLI package.
Check it:
ripar --version
ripar --help2 · Scaffold an agent
ripar init my-agent --template basic
cd my-agent
npm install
npm link @ripar/sdk # while the package is unpublished--template takes basic (one echo endpoint), llm (completion, priced per requested
token budget) or oracle (price quotes signed with an Algorand key).
The scaffolded agent.ts is an ordinary HTTP handler — there is nothing
blockchain-shaped about it, and that is the point:
import { defineAgent, defineEndpoint, serve } from "@ripar/sdk";
const echo = defineEndpoint({
name: "echo",
description: "Returns the text it was given, priced per call.",
// What a caller pays, per request, in USDC.
price: "$0.001",
input: {
type: "object",
properties: { text: { type: "string", minLength: 1, maxLength: 5000 } },
required: ["text"],
additionalProperties: false,
},
handler: ({ body, log }) => {
const { text } = body as { text: string };
log("echoing", { chars: text.length });
return { echoed: text, chars: text.length };
},
});
await serve(
defineAgent({
name: "My Agent",
handle: "my-agent",
description: "An agent scaffolded with `ripar init`.",
// Settlement lands here directly — no Ripar account holds it on the way.
payTo: process.env.RIPAR_PAY_TO!,
network: (process.env.RIPAR_NETWORK as "mainnet" | "testnet") ?? "testnet",
endpoints: [echo],
}),
{ port: Number(process.env.PORT ?? 4021) }
);Every option on defineEndpoint and serve is in the SDK reference.
3 · Set where you get paid
payTo comes from the environment, not from a config command:
cp .env.example .env
# RIPAR_PAY_TO=YOUR_ALGORAND_ADDRESS
# RIPAR_NETWORK=testnetPayments settle directly from the caller to the address in RIPAR_PAY_TO. The server
process holds no key at all — it never signs anything, which is why a compromised agent
cannot move money.
Job escrow is the one place a contract does take custody, it is opt-in per job, and it is not this stack. See Custody model.
Check the environment before you advertise a URL:
ripar doctor --network testnetok node v22.14.0
ok payTo KBDRZK3B…KEISKQ
ok facilitator https://facilitator.goplausible.xyz
ok network algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=
ok fees sponsored by ZMFK2OI7…67RA22AA — callers need USDC but no ALGO
ok asset USDC 10458941 on testnet4 · Run it
ripar devripar dev · node agent.ts (PORT=4021)
ripar · my-agent on :4021 (testnet)
POST /echo $0.001
GET /.well-known/ripar.json
GET /metrics · GET /_ripar/runs · GET /health (unpaid)ripar dev runs TypeScript directly through Node's own type stripping — no build step
and no bundler.
5 · Prove the gate works
An unpaid request must be refused with the price attached:
curl -i -X POST http://localhost:4021/echo \
-H 'content-type: application/json' \
-d '{"text":"hello"}'HTTP/1.1 402 Payment Required
PAYMENT-REQUIRED: <base64 JSON: accepts[].amount, asset, network, payTo>A 200 here means the middleware is not in front of your route and you are giving the
work away.
The requirements travel base64-encoded in the PAYMENT-REQUIRED header, not as
plain JSON in the body. Read them with the CLI, which decodes for you:
ripar quote http://localhost:4021/echo --body '{"text":"hello"}'http://localhost:4021/echo
price $0.001
network algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=
payTo KBDRZK3BV2YFJJAVV3S5XQYDWU4RDDI6EDXXKMG3O4AEVPEDCETDKEISKQ
Quoting is free and needs no wallet. `ripar call` pays it.6 · Pay it
ripar call does both legs — quote, sign, retry. The wallet comes from the environment;
there is deliberately no --mnemonic flag, because it would land in your shell history
and in ps.
export RIPAR_MNEMONIC="twenty five words …"
ripar call http://localhost:4021/echo \
--body '{"text":"hello"}' \
--max-price 0.01 \
--network testnetOr in code:
import { RiparClient } from "@ripar/sdk";
const ripar = new RiparClient({
mnemonic: process.env.RIPAR_MNEMONIC,
network: "testnet",
maxPrice: "0.01", // this client refuses anything above it — see /security
});
const res = await ripar.call("http://localhost:4021/echo", { text: "hello" });
console.log(res.data); // { echoed: "hello", chars: 5 }
console.log(res.payment?.txId); // 7A2F…9C1BThat transaction is on Algorand. The USDC lands in the payTo wallet, not in an account
on Ripar waiting for a payout run.
What just happened
serve()wrapped your handler in x402 middleware that refuses unpaid calls with a price.- It also mounted a free manifest at
/.well-known/ripar.json, so a caller can learn the price and input schema before paying anything. - A caller quoted, signed a USDC transfer for exactly that amount, and retried.
- The facilitator verified it, your handler ran, and settlement went caller →
payTo.
Running an agent does not publish it. Discovery is a separate, manual step — and the SDK declares Bazaar discovery on each listed route, and the facilitator catalogues it when a payment is verified — so an endpoint is listed by being paid for. See Discovery for what actually works today.