Tabdocs.tab.xyz
Start heretab-v3

Quickstart

Four steps. At the end of them, an agent with no wallet, no key and no USDC has paid a seller and you can read the receipt on HCS. The numbering is a sequence — order carries information.

1

Read a tab — no install, no key, no account

Every figure the console shows is served over plain HTTP from published data. Nothing here is privileged: try it against the live testnet gateway right now. A brand-new tab starts at a 0.2500 floor.

bash
curl https://tab-gateway.onrender.com/v1/tabs/0.0.10390398

# {"balance":"4.750000","available":"0.250000",
#  "ceiling":"0.250000","perCallCap":"0.050000", ... }
2

Spend from it

One POST. The agent holds no key and signs nothing — the gateway pays the seller on its behalf, against a ceiling the agent earned. You do not name a price: the seller states it in its x402 challenge and the gateway reads it.

bash
curl -X POST https://tab-gateway.onrender.com/v1/spend \
  -H 'content-type: application/json' \
  -d '{"tab":"0.0.10390398",
       "url":"https://tab-seller.onrender.com/feed/25"}'

# {"paid":{"amount":"0.050000","seller":"0.0.10379572",
#          "receiptSeq":60,"elapsedMs":4012}, "body":{...}}
3

Handle the refusal

A refusal is a 200 with a rule, not an error to catch. It is the product working. Ask for more than the 0.0500 per-call cap and watch it fire.

bash
curl -X POST https://tab-gateway.onrender.com/v1/spend \
  -H 'content-type: application/json' \
  -d '{"tab":"0.0.10390398",
       "url":"https://tab-seller.onrender.com/feed/100"}'

# {"refused":{"rule":"PER_CALL_CAP",
#   "reason":"Spend of 0.2000 refused. The request exceeds
#             the per-call cap of 0.0500 in force for this tab.",
#   "evidence":{"requested":"0.2000","cap":"0.0500"},
#   "retryable":false}}
4

Check the arithmetic yourself

The ceiling carries the inputs it was computed from and a hash of them. Rerun the scoring code on those inputs and you get the same hash — without our database, and without trusting this page.

bash
curl https://tab-gateway.onrender.com/v1/tabs/0.0.10390398/ceiling

# {"ceiling":"0.250000","binding":"starter_floor",
#  "model":"tab-v3","seq":55,
#  "hash":"c4289169bd1e...",
#  "inputs":{"revenue":"0.000746","tier":"Unrated", ... }}
The agent signed nothing.
It holds no USDC. No key was provisioned. The gateway paid the seller from house float and moved your running balance by 0.0400.

Reading the callouts

Four kinds, distinguished by border and label. REFUSES is Tab-specific and appears on every endpoint that can decline.

Note
Amounts are USDC with 6 decimals, carried as integer strings. Tab displays four and truncates — the truncation is deliberate and never rounds up.
Important
`max` is optional and omitting it is the normal case. The seller sets the price in its 402 challenge; supply `max` only to refuse anything above a figure of your own.
Refuses
Spending can be refused. A refusal is a successful HTTP response carrying the rule, a plain reason, the evidence, and whether retrying could ever help.
Testnet
Everything here is Hedera testnet with a test USDC token. The money is not real; the consensus, the receipts and the arithmetic are.

POST /v1/spend

Requests a spend against the tab. The gateway performs the six fast-path checks, writes a hold, pays the seller over x402, and returns the seller’s response body.

spend(url: string, max: Usdc, idempotencyKey?: string) → SpendResult
Parameters
NameTypeMeaning
tabstringWhich tab pays. A Hedera account id, e.g. 0.0.10390398.
urlstringThe x402 endpoint to call. The seller needs no knowledge of Tab.
maxUsdc?Optional. The most to pay for this one call, as a decimal string — never a float. Omit it to accept the seller's quoted price.
idempotencyKeystring?Replays return the original result rather than spending twice.
Refuses
Handling refusal is the agent developer’s main job. Every code this endpoint can return, and the condition that fires it:
Refusal codesevery row is linkable
CodeFires whenAgent shouldRetry?
PER_CALL_CAPThe request exceeds the 0.0500 per-call cap in force for this tab.Split the work, or quote first and wait for a ceiling raise.never
WINDOW_CAPWindow spend plus this request would pass the per-window cap.Retry after the window tick. The countdown is in the response.later
CEILING_EXCEEDEDOutstanding plus holds plus this request would pass the ceiling.Earn first, or wait for settlement to clear outstanding.later
CONTROL_CLUSTERThe seller is inside the agent's funding ancestry within the hop limit.Buy from an independent seller. Do not retry the same one.never
SELLER_NOT_ALLOWLISTEDA Starter Tab may only buy from allowlisted sellers.Graduate the tab with one clean settlement, then retry.never
TAB_FROZENThe tab is frozen pending operator review.Stop spending. Surface the freeze to the operator.never

The ceiling formula

Displayed as arithmetic, with each term labelled, because a reader has to be able to check it against the CEILING view in the console.

ceiling = min(
trailing_attested_revenue×tier_multiple×ramp_factor,
hard_cap[tier]
)
tier_multiple  A 3.0 · B 2.0 · C 1.0 · Unrated 0
ramp_factor   starts 15%, +15% per clean settlement, −30% per missed
a tab below its starter floor is raised to the floor unless the tier is Unrated

HCS receipt message

json · a message off the receipts topictopic
{
  "kind": "refusal",
  "at": "1789101793.110272959",
  "window": 5963672,
  "seq": 63,
  "token": "0.0.429274",
  "counterparty": "0.0.10379572",
  "requested": "0.200000",
  "rule": "PER_CALL_CAP"
}

Spend, as a sequence

agentno walletgateway6 checks · holdpays from floatsellerplain x402spend()402 → pay200 bodyresultHCS receipt written after settle-of-hold · balance −0.4421 → −0.4821

The ten messages

This is the whole protocol. Everything the console shows, everything verify-tab checks and everything a stranger can replay is one of these — there is no private side-channel and no stored row that is not derived from a message on one of the three topics. Field names are the wire names, kept short because HCS charges by the byte and a receipt that needs chunking is one that can arrive in pieces.

TypeWritten byTopicFieldsWhen
holdgatewayreceiptshold · cp · amt · exp · reqCredit is reserved, BEFORE the seller is called. Awaited to consensus.
debitgatewayreceiptscp · amt · hold · req · txThe seller was paid. Carries the hold it commits and the settlement tx.
creditgatewayreceiptscp · amt · att · req · txThe agent earned. `att` records whether the gateway served what was paid for.
refusedgatewayreceiptscp · amt · rule · evA spend was refused. Published, not logged — the rule and its evidence.
repairsettlementreceiptscp · amt · tx · whyA correcting entry. Append-only means a mistake is fixed forward, never erased.
weightengineceilingscp · bp · why · block · rev · share · modelWhat one counterparty’s revenue is worth, and every reason applied.
factengineceilingsacct · born · byWho created an account and when. Observed once, remembered permanently.
ceilingengineceilingsceil · computed · bind · inputs · model · hash · causeThe limit, its inputs, and a hash of them. `computed` differs from `ceil` when growth is held.
settlementsettlementsettlementscredits · debits · interest · net · n · tx · outcome · rampFrom · rampTo · outstandingA window closed. One transfer for the net of everything in it.
registerengineceilingsroot · ceil · perCall · allowlist · uaidA funding root claimed, once, permanently. First claim wins.

The write-ahead order

reserve → pay → commit. The hold is published and awaited to consensus before the seller is called. That costs two to four seconds and buys the one thing a stranger cannot otherwise check: that every debit was authorised before the money moved.

1check
Cheapest rule first, against cached state
A refusal costs no chain write on the decision path and never calls the seller. Published afterwards so the refusal is auditable too.
2reserve
hold published to the receipts topic, awaited to consensus
Headroom is committed before anything is spent. A failed hold FAILS CLOSED — the seller is never called.
3pay
x402 settlement to the seller, hold id as the idempotency key
A retry with the same hold cannot pay twice. The seller sees an ordinary x402 customer and never learns Tab exists.
4commit
debit published, carrying the hold it closes and the settlement tx
The debit names its hold, so `verify-tab` can assert `debit_within_hold` — every debit traces to an authorisation that preceded it.
5expire
An uncommitted hold lapses on its own at `exp`
A crash between pay and commit strands nothing permanently. Releasing early instead would let the agent spend the same headroom twice.

How revenue is weighted

A counterparty’s revenue is not worth its face value. Three reasons zero it outright — the revenue is not independent demand in any amount — and the rest multiply, truncating down at each step in a fixed order, so the result never depends on evaluation order and never rounds in the agent’s favour.

Blocking · weight becomes zero
FUNDED_BY_AGENTthe agent funded this account — revenue from it is the agent paying itself
COMMON_FUNDERthe same account funded both this counterparty and the agent’s tab — one operator on both sides of the trade, so the revenue is not independent demand
SOLE_COUNTERPARTYthe agent is this account’s only counterparty — it exists to trade with the agent
Discounting · these multiply
SHARED_FUNDING_ROOT×0.70
YOUNG_ACCOUNT×0.60
CONCENTRATED×0.80
RECIPROCAL_FLOW×0.50
UNVERIFIED_FUNDING×0.50
UNATTESTED×0.60

HCS-14 identity

The agent is addressable as a UAID, not only as an account number — and the identifier is derived from what the agent is, so two parties computing it from the same facts get the same string and nobody has to be trusted to issue it.

text · derivation
name · nativeId · protocol · registry · skills · version

1. Six fields, canonicalised — keys sorted, skills sorted numerically.
2. SHA-384 over the canonical bytes.
3. Base58 of the digest. That is the AID.
4. uaid:aid:{base58};uid=…,registry=…,proto=…,nativeId=…

uaid:aid:4ArPMKdhwEoLXJo7UaxCMD3H4PKKKrDRc4CTAQba4uZqzVDBM6Z4MqDhp1DqrEBwnV

Parameters are emitted in the documented order and an absent one is omitted rather than left empty — a trailing `nativeId=` would be a claim about an identifier nobody has.

Build on Tab

Four surfaces over one gateway, and deliberately the same ten verbs: @tab/sdk defines the interface, and the MCP server, the Agent Kit plugin and the CLI all bind to it. Two of the ten act; the other eight only read what was published.

Published on npm as @0xdivyanshh/tab-*. The HTTP API needs no install at all — which is why the quickstart above is curl.

MCP — give your own LLM a tab

The shortest path to "my agent can spend money". Add this to Claude Desktop and the model gets seven tools: tab_balance, tab_quote, tab_ceiling, tab_counterparties, tab_receipts, tab_health and tab_spend. It holds no key and cannot produce a transaction — every spend goes through the gateway, against the ceiling.

json
{
  "mcpServers": {
    "tab": {
      "command": "npx",
      "args": ["-y", "@0xdivyanshh/tab-mcp"],
      "env": {
        "TAB_GATEWAY_URL": "https://tab-gateway.onrender.com",
        "TAB_ACCOUNT_ID": "0.0.10390398"
      }
    }
  }
}

SDK — ten verbs, two of which act

Eight read and two spend. The read methods return what was PUBLISHED rather than recomputing it: the package cannot import the scoring or graph code, so it is structurally incapable of offering a second opinion about a ceiling.

typescript
// npm i @0xdivyanshh/tab-sdk
import { createTab } from '@0xdivyanshh/tab-sdk'

const tab = createTab({ baseUrl: 'https://tab-gateway.onrender.com' })

const result = await tab.spend({
  tab: '0.0.10390398',
  url: 'https://tab-seller.onrender.com/feed/25',
  // max is optional — the seller's 402 sets the price
})

if (result.outcome === 'refused') {
  result.rule      // 'PER_CALL_CAP'
  result.guidance  // what to do instead
  result.retryable // false
}

CLI — the same verbs from a terminal

A refusal exits ZERO, on purpose: it is a normal outcome, and a non-zero exit would teach every shell script that refusing is a fault.

bash
export TAB_GATEWAY_URL=https://tab-gateway.onrender.com
export TAB_ACCOUNT_ID=0.0.10390398

tab status
tab ceiling
tab counterparties
tab spend https://tab-seller.onrender.com/feed/25

Verify — recompute a published ceiling

The claim this whole project rests on. It reads the ceiling topic through a public mirror node, reruns the real scoring code on the published inputs, and compares both the number and the hash. It needs no database and no credential of ours — which is the point, and why the scoring package has no I/O.

bash
TOPIC_CEILINGS=0.0.10182697 pnpm verify-ceiling

# checks
#   seq 55  0.250000  recomputed 0.250000  hash matches
#   ...