# Uptime > A status page for AI agents. Uptime's server checks agent endpoints about every 5 minutes and writes each endpoint's daily uptime (checks, answers, p50 and p95 latency, and every single check) to Uptime's own log contract on Robinhood Chain, so anyone can look an agent up before paying it and re-hash the record. What Uptime's server saw from one Vercel region. It does not prove the agent is honest, correct or safe to pay, or what users elsewhere saw. No AI model is involved: the checks are plain code, and every record is public. ## How the checks work - **What:** one HTTP GET to the endpoint URL. A check is **up** when the final answer is HTTP 200-299 within 6 seconds (up to 3 redirects included). Anything else is **down**: another status, a timeout, a DNS, TLS or connection error. The latency is the time to the answer's headers. - **From where:** Uptime's server on Vercel, in one region (iad1, Washington, D.C., unless the project's region changes). Every daily record names the regions its checks ran from. It is one vantage point, not a global probe network. - **How often:** a job runs about every 5 minutes (Vercel's cron, plus page visits; never more than once in 4 minutes) and checks up to 60 endpoints per run. Uptime monitors at most 60 endpoints at once, so every monitored endpoint is checked every run. When all slots are taken, adding a new one answers 409 `full` and charges nothing. - **Safety:** only https on port 443, host names only (no IP addresses), every address the name resolves to must be public (checked by the connection's own DNS lookup, so redirects can't reach private hosts either), no cookies, at most 16 KB of the answer is read. Requests say `User-Agent: Uptime/1.0 (+https://uptimeagents.online)`. - **Offchain vs onchain:** the checks run on Uptime's server and are offchain until the day closes. After each UTC day, one record per endpoint is published and its sha256 written to the log as a DAY entry. Monitoring periods and slots are kept by Uptime's server (offchain). ## Add your agent ```bash # 1. Start a claim. wallet and agent (an ERC-8004 agent id owned by that wallet) are optional. curl -X POST https://uptimeagents.online/api/uptime/claim -H "content-type: application/json" \ -d '{"url":"https://youragent.xyz/health","wallet":"0xYourWallet","agent":1234}' # -> { code, file: { url: "https://youragent.xyz/.well-known/.txt", body: "" }, message, expiresAt } # 2. Serve the code on the endpoint's own host (HTTP 200, the code on a line of its own) # https://youragent.xyz/.well-known/uptime-abcdefgh2345.txt -> uptime-abcdefgh2345 # If you linked a wallet, sign the exact message (no gas, no transaction): cast wallet sign --private-key $AGENT_KEY "$MESSAGE" # 3. Start monitoring: 2 credits per day, 1 to 90 days per call curl -X POST https://uptimeagents.online/api/uptime/add -H "content-type: application/json" -H "Authorization: Bearer ck_…" \ -d '{"code":"uptime-…","days":30,"signature":"0x…"}' # -> { added: true, ep, monitoredUntil, claim: { entry, uri, hash }, page } # Anyone can add days to a claimed endpoint curl -X POST https://uptimeagents.online/api/uptime/extend -H "content-type: application/json" -H "Authorization: Bearer ck_…" \ -d '{"ep":"youragent-xyz-1a2b3c","days":30}' ``` - A claim lasts 24 hours and starts monitoring once. Proving the host stops anyone from pointing Uptime at a site they don't run. - `add` checks the file, the signature and the agent id before anything is written. A bad signature is a 400 before any charge. A failed file or agent check is a 422 `proof_failed` (refunded); an unreachable host is a 503 `upstream` (refunded). When every check passes, the CLAIM record is published and written to the log, then monitoring starts. - The URL you claim is public on the status page and in the records. Don't put secrets in it. - Monitoring runs until the paid days run out. There is no early stop and no refund for unused days. Before $UPTIME launches, `add` runs every check for free and returns `{ added: false, preview: true, passed, checks }`: nothing is monitored or written. The site monitors a few public status endpoints (seeds) so you can see real checks; they are labelled as seeds, are not agents and were not claimed by their owners. ## Costs | call | cost | |---|---| | POST /api/uptime/claim | free | | POST /api/uptime/add `{ code, days, signature? }` | 2 credits per day, refunded if a check or the write fails | | POST /api/uptime/extend `{ ep, days }` | 2 credits per day | | GET /api/uptime/status?url=… or ?ep=… | 1 credit: up or down now, last check, 24 h, 7 and 30 day uptime, the latest onchain day | | GET /api/uptime/history?ep=…&days=30 | 2 credits: up to 90 daily records, each with its onchain entry | | GET /api/uptime/search?q=…&wallet=…&agent=…&min=99.5&active=1 | 1 credit: up to 50 endpoints, best 30-day uptime first | | GET /api/uptime/board, /api/uptime/page?ep=…, /api/uptime/stats | free, cached about 30 seconds (the site's own data) | | GET /api/uptime/badge.svg?ep=… | free, cached about 5 minutes | Free tier for the paid lookups: 20 calls per IP per UTC day (2000 for everyone). An endpoint that isn't monitored answers 404 `not_monitored` before anything is charged. Monitoring days always need credits. ## Look an agent up ```bash curl "https://uptimeagents.online/api/uptime/status?url=https://youragent.xyz/health" -H "Authorization: Bearer ck_…" curl "https://uptimeagents.online/api/uptime/history?ep=youragent-xyz-1a2b3c&days=30" -H "Authorization: Bearer ck_…" curl "https://uptimeagents.online/api/uptime/search?wallet=0x…" -H "Authorization: Bearer ck_…" ``` `status` answers `{ ep, url, status: "up" | "down" | "unknown", last: { at, status, ms, error }, h24, d7, d30, today, lastRecord: { day, uptime, p50, p95, entry }, verify, page }`. `h24` and `today` come from the live checks (offchain); `d7` and `d30` from finished days. Uptime is up/checks as a percentage, rounded down to 2 decimals, so 100 means every check answered 2xx. The status page for people: https://uptimeagents.online/status.html?ep=. Embed a live badge: `Uptime`. ## Verify a day yourself 1. Read the DAY entry (`/api/log/entry?entry=` or `cast call`, below) and take its `uri` and `hash`. 2. `curl -s "" | shasum -a 256` must equal `hash` without the 0x. 3. The record's `samples.rows` holds every check: [second of the UTC day, HTTP status (0 = no answer), ms, error code]. Recount: `up` is the rows with status 200-299, `uptime` = floor(up * 10000 / checks) / 100, and `p50`/`p95` are the nearest-rank percentiles (the ceil(p/100 * n)-th smallest) of the ms of the up rows. 4. The entry's `subject` is keccak256 of the endpoint URL exactly as in the record (`cast keccak "https://youragent.xyz/health"`), and its `ref` is the endpoint's previous DAY entry, so each endpoint's days form a chain. Only entries written by the log's operator (`operator()`) are Uptime's records. Anyone else can write to the log by burning the fee, and those entries mean nothing here. ## The onchain log Uptime keeps a public record onchain in its Log contract. Each entry is written before the record it points at goes out, so its time can't be faked later. **Nothing in the log can be edited or deleted.** The contract has no function that changes, removes or reorders an entry, for anyone: not the writer, not Uptime's agent, not the owner. Ids count up from 1 in the order entries were written. Each entry has: - `author`: the address that wrote it. - `time`: the block time it was written at, in unix seconds. - `kind`: what sort of record it is (table below). - `ref`: 0, or the id of an earlier entry it answers, such as the call a score settles. - `subject`: a bytes32 saying what it is about. An address is left-padded to 32 bytes, a 32-byte value is used as is, and anything else is the keccak256 of a short string, exactly as written. - `hash`: the sha256 of the full record. - `uri`: where the full record is published (up to 256 bytes). ### Kinds | kind | name | what it records | |---|---|---| | 1 | DAY | One endpoint's uptime for one UTC day. subject: keccak256 of the endpoint URL. ref: the endpoint's previous DAY entry (or its CLAIM, or 0). The record holds every check (second, HTTP status, latency, error) so anyone can recount the totals and percentiles. | | 2 | CLAIM | An owner proved control of an endpoint's host and started monitoring. subject: keccak256 of the endpoint URL. ref: 0. The record holds the URL, the code, the file check, and the linked wallet's signature and ERC-8004 agent id, if any. | ### Read entries with cast ```bash # How many entries there are (the newest id) cast call "count()(uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com # One entry: (author, time, kind, ref, subject, hash, uri) cast call "entry(uint256)((address,uint64,uint32,uint256,bytes32,bytes32,string))" 1 --rpc-url https://rpc.mainnet.chain.robinhood.com # Up to 100 entries from an id, oldest first cast call "entries(uint256,uint256)((address,uint64,uint32,uint256,bytes32,bytes32,string)[])" 1 100 --rpc-url https://rpc.mainnet.chain.robinhood.com # Every entry as an event, with its transaction and the tokens burned (raise --from-block if the RPC limits the range) cast logs --address "Logged(uint256 indexed id, address indexed author, uint32 indexed kind, bytes32 subject, bytes32 hash, uint256 ref, string uri, uint256 burned)" --from-block 0 --rpc-url https://rpc.mainnet.chain.robinhood.com # Subjects: an address left-padded to 32 bytes, or the keccak256 of a string cast abi-encode "f(address)" 0x… cast keccak "PEPE" ``` ### Verify a record 1. Read the entry and take its `uri` and `hash`. 2. Fetch the uri and hash the exact bytes: `curl -s "" | shasum -a 256` (or `sha256sum`). 3. Compare with `hash` without the `0x`. If they differ, the record is not what was logged. ### Read the log over HTTP - GET https://uptimeagents.online/api/log/entries?n=20: the newest entries, newest first (n up to 100). - GET https://uptimeagents.online/api/log/entries?from=1&n=100: entries from an id, oldest first. - GET https://uptimeagents.online/api/log/entries?author=0x…&n=20 (or `kind=1`, or `subject=`): the newest matches among the newest 1000 entries. - GET https://uptimeagents.online/api/log/entry?entry=12: one entry. Add `&verify=1` to have the server check the record's sha256 too. - GET https://uptimeagents.online/api/log/stats: entry count, fee, total burned, % of supply burned, operator and whether writing is paused. Entries come back as `{ id, author, time, kind, ref, subject, hash, uri }`. Before launch these return 503 `not_launched`. ### Log rules - Ids start at 1. `ref` must be 0 or an earlier id. `uri` is at most 256 bytes. - Uptime's agent (the operator) writes for free, one entry at a time or up to 50 in one transaction. Anyone else burns `fee()` $UPTIME per entry. The contract counts what reaches the dead address in `totalBurned()`. - The contract never holds tokens. The owner can pause new writes, change the operator, and set the fee up to 10,000,000 $UPTIME. That is all: no edits, no deletes, no withdrawals. Entries stay readable while writing is paused. ### Log contract - Chain: Robinhood Chain, chain id 4663, RPC https://rpc.mainnet.chain.robinhood.com - Log: - $UPTIME token: - Explorer: https://robinhoodchain.blockscout.com ## Credits: how calls are paid Paid calls spend credits. You buy credits by burning $UPTIME through the Credits contract: the tokens go from your wallet straight to the dead address and are gone for good. The server keeps a ledger per wallet: available = bought onchain - spent + refunded. $UPTIME has not launched yet, so only the free tier is open. You can already get a key, so your agent is ready on day one. | Call | Credits | |---|---| | `uptime/status` | 1 | | `uptime/history` | 2 | | `uptime/search` | 1 | | `uptime/add` | 2 | | `uptime/extend` | 2 | Free tier: 20 calls a day per IP without a key (UTC day), with a cap of 2000 free calls a day for everyone together. Once $UPTIME is live, calls with a key always spend credits; before that, they use the free tier. Prices and totals: GET https://uptimeagents.online/api/credits/info ### Get an API key Sign this exact message (EIP-191 personal_sign) with the wallet that will hold the credits. `issuedAt` is the current time in ISO 8601 and must be within 10 minutes of the server's clock. Smart-contract wallets (ERC-1271) work too. ``` Uptime API key Wallet: Issued: ``` ```js import { privateKeyToAccount } from 'viem/accounts'; const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY); const issuedAt = new Date().toISOString(); const message = `Uptime API key\nWallet: ${account.address}\nIssued: ${issuedAt}`; const signature = await account.signMessage({ message }); const res = await fetch('https://uptimeagents.online/api/credits/key', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ wallet: account.address, issuedAt, signature }), }); const { key } = await res.json(); // "ck_…", shown once: store it ``` Send it on every call as `Authorization: Bearer ck_…` or `x-api-key: ck_…`. Each response says `x-credits-mode` (paid, free or preview) and `x-credits-left`. - GET https://uptimeagents.online/api/credits/balance (with your key) or `?wallet=0x…`: bought, spent, refunded, available and your last 50 calls. - POST https://uptimeagents.online/api/credits/revoke (with your key): deletes that key. Get a new one any time. ### Buy credits Cost = credits × price, in token base units (18 decimals). `maxBurn` is the most you agree to burn, so a price change can't surprise you. `buyFor` tops up any wallet, such as your agent's; `buy(uint256 credits, uint256 maxBurn)` buys for yourself. ```bash # Price per credit, in base units cast call "price()(uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com # Approve credits × price cast send "approve(address,uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com # Buy for , burning at most cast send "buyFor(address,uint256,uint256)" --rpc-url https://rpc.mainnet.chain.robinhood.com ``` Credits count as soon as the transaction lands. Or buy on the site: https://uptimeagents.online/#credits ### Errors This is plain HTTP 402 with buy instructions, not x402. ``` 401 { "error": "bad_key", "message": "…" } the key is wrong or revoked 402 { "error": "credits", "need": 2, "available": 0, "wallet": "0x…", "buy": { "chainId", "contract", "token", "ticker", "pricePerCredit", "call", "approve", "page" } } 402 { "error": "free_limit", "message": "…", "resetsAt": "…", "launched": true, "need": 2, "buy": { … } } no key, and today's free calls are used up 429 { "error": "free_limit", "message": "…", "resetsAt": "…", "launched": false } before launch, free calls used up ``` `pricePerCredit` is in whole tokens as a string. `call` is `buyFor(address account, uint256 credits, uint256 maxBurn)`. ### Refunds If a paid call fails after it was charged (a model error or a timeout, say), its credits go straight back to your balance. Every refund, paid or free, is listed in a public log: GET https://uptimeagents.online/api/credits/refunds?limit=50 (max 500), newest first, as { t, id, route, cost, wallet, reason, detail, mode }. ### Credits contract rules - `buy` and `buyFor` burn credits × price and add the credits to `bought(wallet)`. At most 1,000,000 credits per purchase. Zero credits or a zero wallet revert, and so does a cost above `maxBurn`. - The owner can pause buying and set the price per credit between 0.001 and 10,000,000 tokens. Nothing else: no withdrawals, no minting credits, no editing balances. The contract never holds tokens. - Reads: `price()`, `bought(address)`, `totalCredits()`, `totalBurned()`, `paused()`. - Credits: - $UPTIME token: ## ERC-8004 agent identity Uptime Monitor is an ERC-8004 agent. It registers in the ERC-8004 Identity Registry on Robinhood Chain (eip155:4663:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432) at launch. - Registration file: https://uptimeagents.online/.well-known/agent-registration.json - Identity Registry: https://robinhoodchain.blockscout.com/address/0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 - Reputation Registry (feedback from other agents): https://robinhoodchain.blockscout.com/address/0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 - Sitemap: https://uptimeagents.online/sitemap.xml