Docs · 12

API reference

Every route: inputs, outputs, status codes.

Base URL https://everynth.org. Requests and responses are JSON unless noted. Routes marked session need the cookie set by POST /api/session; the browser sends it automatically. Errors are { "error": "message" } with a 4xx status.

Session#

POST/api/sessionpublic

Sign in with a wallet signature.

{ "wallet": "<base58>", "issuedAt": 1758445200000, "signature": "<base64 ed25519 over the login message>" }
→ 200 { "wallet": "<base58>" } + Set-Cookie everynth_session (httpOnly, 7 days)
→ 400 missing fields · 401 invalid or expired signature (5-minute window, host-bound)

The message to sign, byte for byte:

${host} wants you to sign in to EVERYNTH.\nThis is free and does not move any funds.\n\nWallet: ${wallet}\nIssued at: ${issuedAt}

DELETE/api/sessionsession

Sign out. Clears the cookie. → 200 { "ok": true }

Rate limits#

Counted per wallet (per IP when there is no session), in fixed windows, in the database — so every serverless instance counts into the same bucket. Over budget returns 429 with a retry-afterheader. Nothing here is near what normal use looks like.

POST /api/session              30 / 10 min
POST /api/products             25 / hour
POST /api/upload               20 / hour
POST /api/orders               40 / hour
POST /api/purchases/:id/review 20 / hour
POST /api/reports              10 / hour
GET  /api/verify              300 / minute   (per IP)

Products#

POST/api/uploadsession

Token exchange for a direct browser upload to blob storage (Vercel Blob client protocol). Use upload() from @vercel/blob/client with handleUploadUrl: "/api/upload". Only application/octet-stream up to 200 MB + 28 bytes is accepted.

POST/api/productssession

Launch a product. multipart/form-data:

title        3–80 chars           description  10–4000 chars
category     AI Agent | API | Dataset | Tool | Research | Service | Community
price        "0.02" … "1000000", ≤ 9 decimals (SOL)
kind         file | secret | github
cover        image/png|jpeg|webp|gif ≤ 1 MB           (optional)

issuedAt     ms timestamp, ≤ 5 min old
signature    base64 ed25519 over the launch message, by the session wallet

kind=file    key (base64, 32 bytes)  fileName  fileType  payloadUrl (our blob host only)
kind=secret  key (base64, 32 bytes)  payload   (Blob: iv‖ciphertext, ≤ 64 KB + 28)
kind=github  repo ("owner/name")     token     (checked live: must have admin on the repo)

→ 200 { "id": "<uuid>" } · 400 validation · 401 bad confirmation · 403 wallet blocked · 413 too large

The launch message, rebuilt server-side from the submitted title, price (in lamports) and kind:

${host} wants you to confirm this launch on EVERYNTH.\nThis is free and does not move any funds.\n\nTitle: ${title}\nPrice: ${lamports} lamports\nDelivery: ${kind}\nCreator: ${wallet}\nIssued at: ${issuedAt}

PATCH/api/products/:idcreator + signature

Edit listing details: title description category price cover (same rules), plus issuedAt and signature. Content is immutable. → 200 { ok } · 401 bad confirmation · 404 not yours.

${host} wants you to confirm: edit listing.\nThis is free and does not move any funds.\n\nItem: ${id}\nTitle: ${title}\nPrice: ${lamports} lamports\nWallet: ${wallet}\nIssued at: ${issuedAt}

POST/api/products/:id/removecreator or admin + signature

Unlist. Body: { "block": true } (admins) also blocks the creator and unlists all their products, plus issuedAt and signature. → 200 { ok } · 401 bad confirmation

${host} wants you to confirm: remove from market.\nThis is free and does not move any funds.\n\nItem: ${id}\nScope: product | product and creator\nWallet: ${wallet}\nIssued at: ${issuedAt}

POST/api/purchases/:id/reviewbuyer

Review a product you paid for. One review per purchase; posting again rewrites it. { "rating": 1-5, "body": "≤ 500 chars" } → 200 { ok } · 400 bad rating or too long · 404 no paid purchase of yours.

GET/api/products/:id/coverpublic

The cover image bytes, or 404. Cached 5 minutes.

Orders and purchases#

POST/api/orderssession

{ "productId": "<uuid>" }
→ 200 { "purchaseId", "reference", "creator", "creatorAmount", "treasury", "fee" }   // lamports
→ 400 own product · 404 not live · 409 already owned (also after chain recovery)

Reuses the caller's open order for the product if one exists, after checking the chain for a payment.

POST/api/orders/:id/confirmbuyer

{ "signature": "<base58>" }      // optional: without it the server searches by reference
→ 200 { "ok": true } · 402 { "error": "payment not found yet" | "underpaid: …" | "transaction failed on-chain" | … }

GET/api/purchases/:id/contentbuyer, paid

file    { "kind": "file",   "fileName", "fileType", "key": "<base64>", "payloadUrl": "https://…blob…", "payload": null }
secret  { "kind": "secret", "key": "<base64>", "payload": "<base64 iv‖ciphertext>", "payloadUrl": null }
github  { "kind": "github", "repo": "owner/name", "githubUser": "octocat" | null }
→ 404 no paid purchase

POST/api/purchases/:id/githubbuyer, paid

{ "username": "octocat" }
→ 200 { "ok": true, "repo": "owner/name" } · 400 bad username · 409 tied to another account · 502 GitHub refused

Ownership#

GET/api/verify?product=&wallet=public · CORS *

→ 200 { "owned": boolean, "since": ISO date | null }. See Ownership check.

Moderation#

POST/api/reportssession

{ "productId", "reason" } (5–1000 chars). One per wallet per product. → 200 { ok }

POST/api/admin/unblockadmin

{ "wallet" } → 200 { ok }. Admins are the wallets listed in ADMIN_WALLETS.

Limits at a glance#

price        0.02 – 1,000,000 SOL         fee   5%, floor(lamports × 500 / 10000)
file         ≤ 200 MB                      secret ≤ 64 KB          cover ≤ 1 MB
login        signature valid 5 min         session 7 days
blocked ext  exe msi bat cmd com scr pif vbs vbe ps1 dll dmg pkg app apk deb rpm jar lnk
NextRunning it yourself →