Index / Network / Integrate

Integrate with Keeper HoldingsSandbox

Everything a partner, a Keeper business or an AI agent needs to build against the sandbox without talking to anyone. Base Sepolia, test USDC, no accounts to request.

Quick start

Three requests that need no sign-in. Then pick the flow for your role below.

curl https://keeperholdings.io/v1/skus
curl https://keeperholdings.io/v1/hold/quote?sku=nvidia-rtx-4090&months=3
curl -X POST https://keeperholdings.io/mcp -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Sign in with a wallet

There are no accounts to request. Any wallet on Base Sepolia is a sandbox credential: call GET /v1/auth/nonce, sign the EIP-4361 message with personal_sign, post it to POST /v1/auth/verify. The response carries a bearer token (also set as the kh_s cookie) valid for 7 days. Use it as Authorization: Bearer … on the routes marked signed-in.

Keeper flow

  1. Sign in, then POST /v1/keeper/apply with display_name and region (ISO country-region, e.g. JP-13).
  2. Post a bond: USDC.approve(bond, amount) then KeeperBond.deposit(amount). Status becomes active once the bond is on chain. Coverage must stay at or above 110% of held value.
  3. When an owner picks you, the receipt appears in GET /v1/keeper/me as INTAKE_PENDING. Confirm arrival with a photo: POST /v1/receipts/{id}/intake (multipart photo). The Worker checks the photo, stores it privately, hashes it, and returns the signed intake attestation.
  4. After the owner mints, call CustodyReceipt.acknowledge(id); the receipt is HELD and the fee schedule starts.
  5. Every period: POST /v1/receipts/{id}/proof returns a one-time code; photograph it next to the sealed item and POST /v1/proofs/{pr}/submit. Working on a computer? POST /v1/capture/token with { "kind": "intake" | "proof", "id": "kr_… | pr_…" } returns a 30-minute URL (/capture/{kind}/{id}?t=…, also as a QR SVG) that a phone opens without any wallet; the phone uploads to the same two routes with ?t= in place of a session. The token is bound to that one receipt or proof request and is never written anywhere. A pass returns a signed ProofAttestation plus releaseOnProof calldata (also listed under pending_release in GET /v1/keeper/me until it is sent); anyone can send it and one period of fees is paidto you. Low-confidence checks go to QUARANTINED for review.
  6. On a delivery request, ship and call markShipped(id, trackingHash).

Owner flow

  1. GET /v1/hold/quote?sku=…&months=… for tier, proof interval, per-period fee and prepay.
  2. Sign in and POST /v1/hold/request with sku_id, keeper_id and optionally a serial (stored only as a hash). Ship the sealed item to the Keeper.
  3. When the Keeper confirms arrival the receipt is VERIFIED and GET /v1/hold/mine carries the intake attestation and ready-made mint calldata. Approve USDC for the escrow, then CustodyReceipt.mint(attestation, signature, periods); the prepaid fees sit in FeeEscrow and travel with the receipt.
  4. Redeem with requestRedemption(id); after shipping, close(id) burns the receipt and refunds unreleased fees.
  5. Sell: approve the Settlement contract for the receipt, sign an EIP-712 Offer and POST /v1/market/offers. A buyer fills it atomically.

Agents: MCP and x402

The MCP server at https://keeperholdings.io/mcp (JSON-RPC 2.0, Streamable HTTP, no SSE) exposes read tools for free: parts, prices, quotes, Keepers, receipts, offers, attestations, chain info and calldata preparation. index_live and GET /v1/index/{sku}/live are paid with x402 v2: 0.01 test USDC on Base Sepolia, facilitator https://x402.org/facilitator. An agent with a funded wallet can quote custody, open a hold, pay fees and buy or sell receipts with nothing but the API and its own signatures.

{ "mcpServers": { "keeper-holdings": { "url": "https://keeperholdings.io/mcp" } } }

Signed attestations

The Worker is the index oracle. GET /v1/attestations returns EIP-712 IndexAttestation messages (sku as keccak256 of the id, price in USDC atomic units, tier, validity window) signed by the attester 0xc948f48082BAE908D803edC53ec1F6753bD056EB. The same key signs IntakeAttestation (what mint verifies) and ProofAttestation (what releaseOnProof verifies). Domain: {"name":"Keeper Holdings","version":"1","chainId":84532,"verifyingContract":"0x8e90A4C0b9EC75aeFA76853B6ccBEa7489B24c08"}.

Contracts (Base Sepolia, chain id 84532)

Every contract refuses any chain id other than 84532 (and 31337 for local anvil). Transfers of receipts are allowed only in HELD and only while transfersOpen is true (sandbox: on; production: off until the pilot gates). Operator powers are pause, dispute resolution behind a 48-hour timelock, and attester rotation behind 24 hours; every use is an event. Test money: the sandbox USDC has an open mint(to, amount) (5,000 per call); gas: any Base Sepolia faucet.

Webhooks

Sign in and POST /v1/webhooks with { "url": "https://…", "events": ["receipt.state", "proof.result", "offer.posted", "offer.filled", "signal.fired", "signal.cleared"] }. The response carries a signing secret, shown once. Every delivery is a JSON POST { id, event, created_at, sandbox, data } with X-Keeper-Signature: sha256=HMAC-SHA256(secret, body), X-Keeper-Event and X-Keeper-Delivery; answer 2xx within 10 seconds. After 20 consecutive failures a hook pauses; POST /v1/webhooks/{id} with { "status": "active" } resumes it. POST /v1/webhooks/{id}/test sends a ping; GET /v1/webhooks/{id}/deliveries lists the last 50. Exit signals fire from the index cron when a trigger on /v1/signals changes state. Polling still works: GET /v1/receipts/{id}, /proofs, /v1/market/offers, /v1/testnet.

OpenAPI

/openapi.json describes every route. Public data is CORS-open; signed-in routes accept the bearer token.