INFINITEOCEAN.IO — API SPEC FOR AGENTS ======================================= Base URL: https://api.infiniteocean.io CORS: * (call from anywhere) Auth: X-Ocean-Key header = your public key. Required for all routes. Secure your routes with share/unshare (or grant/revoke). Fifty-eight endpoints + access control + view subdomain + built-in MCP server.
Drops-first
All features are accessible via POST /exec using the Drops language. HTTP endpoints still work but Drops is the recommended interface. See /drops for the full language reference.
This spec page documents both Drops commands and the remaining HTTP-only endpoints.
-- Save an event (no key = append-only) save myapp/events/signup action: "click" -- Save an entity (with key) save users @user-123 name: "Alice" email: "alice@example.com" -- Get it back users @user-123 -- Update (partial merge) update products @sku-42 price: 29.99 on_sale: true -- Remove it remove users @user-123Route rules: - Segments: a-z A-Z 0-9 - _ . - No wildcards, no leading/trailing slashes, no empty segments - 1-10 segments, max 512 chars - Example: deploy/myapp/prod Entity key rules (optional): - Characters: a-z A-Z 0-9 - _ . - Max 256 characters. No slashes. - Same route + key = versions of one entity. Latest write wins. - payload: null with a key = deleted (entity is removed). CRUD: SAVE: save users @user-123 name: "Alice" GET: users @user-123 UPDATE: update users @user-123 email: "new@x.com" REMOVE: remove users @user-123 LIST: users Note: JSON object syntax { "key": "value" } still works (backwards compatible). Aliases: write = save, read = get, delete = remove, patch = update, query = find 2. GET /stream/:prefix — Replay history + realtime via SSE ────────────────────────────────────────────────────────── Request: GET https://api.infiniteocean.io/stream/myapp/*?last=100 Prefix examples: /stream/myapp/* → everything under myapp/ /stream/myapp/events/* → all myapp events /stream/myapp/events/signup → exact route only /stream/* → everything (use with caution) Query params: last=N last N matching drops, then go live (most common) from=0 replay all history, then go live from=
-- Text search (keyword + fuzzy matching) search myapp/logs "deployment error" top 10 -- Vector search (send vector via POST /exec body) search vectors/memory top 10 -- Hybrid search (text + vector combined) search vectors/memory "hello world" top 10Three modes: - Vector only: send "vector" → vector search (cosine similarity) - Text only: send "text" → keyword search with fuzzy matching - Hybrid: send both → merged results using selected fusion strategy Fusion strategies (hybrid mode only): - "weighted" (default) — normalize both score sets to [0,1], combine as 0.5 × vector + 0.5 × text - "rrf" — Reciprocal Rank Fusion: score = Σ 1/(60 + rank). Ignores raw scores, uses rank position only. More robust when score distributions differ. Industry standard for RAG. Response metadata: "searched" — number of vectors/documents actually compared during search "total_vectors" — total indexed vectors at this prefix (vector/hybrid modes only) "index_type" — "hnsw" (graph index, ≥500 vectors) or "brute_force" (<500 vectors) "dimensions" — vector dimensionality (vector/hybrid modes only) "time_ms" — search duration in milliseconds "mode" — "vector", "text", or "hybrid" "fusion" — "weighted" or "rrf" (hybrid mode only) Vector indexing: Prefixes with fewer than 500 vectors use exact brute-force search (100% recall). At 500+ vectors, an HNSW graph index is built automatically for O(log n) search with 95–99% recall. The index is persisted to disk — subsequent searches on the same prefix warm instantly from the saved index instead of re-scanning all drops. Compute cost is based on "searched" (vectors visited), not total vectors. Text search details: - Extracts all string values from drop payloads (including entity keys) - Keyword ranking with fuzzy matching - Fuzzy matching: misspelled words (edit distance ≤ 1) still match with slight score penalty - Skips "vector" fields in payloads (numeric arrays, not text) - Text index is also persisted to disk for instant warm on subsequent searches Filtered search: Add "filter" to narrow search results by metadata. Same MongoDB-style operators as query ($eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $contains, $startsWith, $endsWith). Filter is applied after search scoring — results are ranked first, then filtered. Use "candidates" (default = top_k) to search for more results before filtering. Vector drop convention — write a drop with a "vector" field in payload: save vectors/docs vector: (0.12, -0.34, 0.56, ...) text: "hello" metadata: {} Any dimensionality works (128, 384, 768, 1536, 3072). All vectors in a prefix must have the same dimensions. Non-vector drops at the same prefix are skipped. 4. find — Filter, aggregate, and join ─────────────────────────────────────────────
-- Filter by field (bare field names, single = for equality) find myapp/events where type = "signup" sort created_at desc limit 10 -- Implicit find (route + where) users where age > 10 sort name limit 5 -- List all current entities find users latest -- Aggregation find myapp/orders where status = "completed" group category sum amount countEntity key deduplication (latest: true): When latest is set, drops are grouped by route+key. Only the newest drop per key is kept. Deleted entities (payload: null) are excluded. Non-keyed drops always included. Filters are applied after deduplication — you query current entity state, not old versions. Filter operators: $eq, $ne equality / inequality $gt, $gte, $lt, $lte numeric/string comparison $in, $nin value in / not in array $exists field exists (true) or missing (false) $contains string contains substring $startsWith string starts with prefix $endsWith string ends with suffix Field paths use dot notation: "payload.metadata.source" resolves nested fields. JSON string payloads are auto-parsed before filtering. Aggregation: Compute functions: $sum, $avg, $min, $max, $count, $first, $last, $distinct $count takes true as its value (not a field path). $distinct returns an array of unique values. group_by can be a string or array of strings. Omit group_by to aggregate all matching drops. Join — combine drops from two prefixes: Left join: every drop gets a _joined object with the matched fields. Drops without a match are still included (without _joined). join.on is [localField, remoteField]. join.select picks which fields from the remote drop. 5. get — Read a single entity ───────────────────────────────
-- Get the latest version of an entity users @user-123 -- Or explicitly: get users @user-123404 NOT_FOUND if the entity was never created. 404 DELETED if the latest version is deleted (payload: null). Payload auto-parsing: Payloads stored as valid JSON strings are automatically parsed into objects on retrieval. Always check typeof payload before calling JSON.parse() — it may already be an object. Version history: Via HTTP: GET /entity/users?key=user-123&history=true → list all versions Via HTTP: GET /entity/users?key=user-123&at=
-- Find with filters find users where status = "active" sort name limit 10 -- Pick specific fields find users where status = "active" then pick name, email -- Group and aggregate find orders group category sum amount count -- Combine routes find orders join users on user_id = key then pick *, name -- Sort and paginate find users sort name limit 10 skip 20 -- Atomic counter add 1 to pages @home views -- Atomic batch bulk (save accounts @alice balance: 900, save accounts @bob balance: 1100)Field mapping (automatic): key, created_at, drop_id, route, source → direct fields (no prefix) everything else → payload.{field} Supported queries: find route where field = "value" find route where field > 10 and field2 contains "text" find route where field in ("a", "b", "c") find route where field exists find route where field starts with "A" find route sort field limit 10 skip 5 find route group field sum amount count find route join route2 on local_field = remote_field find route then pick field1, field2 count route count route where field > 10 Filter operators: =, !=, >, <, >=, <=, in, not in, contains, starts with, exists Aggregate functions: sum, avg, min, max, count, first, last, distinct Saving without a key auto-generates a UUID key. Queries return current entity state (latest version, not history). Syntax notes: - `then` replaces `|` for pipes (e.g. find users then pick name). `|` still works. - `pick` replaces `pluck` (e.g. pick name, email). `pluck` still works. - `skip` replaces `offset` (e.g. limit 10 skip 5). `offset` still works. - `()` replaces `[]` for arrays (e.g. bulk (save ..., save ...)). `[]` still works. - Bare field names replace `.field` (e.g. pick name not pick .name). Dot prefix still works. - Key:value pairs replace `{}` in save/update (e.g. save users @id name: "Alice"). JSON `{}` still works. Schemas: define "route" fields name: "text required" email: "text" age: "number" Supported types: text, number, boolean Constraints: required Validation runs on save and update. Atomic batches: bulk (save a @x ..., save b @y ...) All writes succeed or none do. 7. Access Control — Route zones and trust drops ───────────────────────────────────────────────── Routes are organized into zones based on the first segment. Access is enforced on all operations (save, get, search, find). Zones: Zone First segment Read access Write access ──── ───────────── ─────────── ──────────── open anything else anyone (or ACL-restricted) anyone (or ACL-restricted) internal _ (single underscore) anyone X-Ocean-Key + entity ownership private
-- Control access to a route share agency/shop with @DEV_KEY @CLIENT_KEY -- Remove access unshare agency/shop from @CLIENT_KEYModes: shared: Both reads and writes require a key in the grant list + matching permission. protected: Reads are public (no key needed). Writes require a key in the grant list + "drop" permission. writeonly: Writes are public (anyone can write). Reads are blocked (403) for non-grant keys. moderated: Reads are public. Writes require authentication (any key). Deletes restricted to grant-list keys. Hierarchy: ACLs inherit down the route tree. When checking agency/shop/products: 1. Check agency/shop/products for _access entity 2. Check agency/shop for _access entity 3. Check agency for _access entity First match wins (most specific ACL takes precedence). Bootstrap: First write of _access to an open route always succeeds (no ACL exists yet). The writer's key is auto-injected as owner. Only the owner can modify _access afterward. Cache: ACL lookups are cached for 30 seconds. Changes take effect within 30s. Internal zone: Routes starting with _ (single underscore): _site, _memory, _cron, _mcp, _config, _handles, _dlq, etc. Publicly readable — anyone can view _site pages, search _memory, etc. Writes require X-Ocean-Key header. First writer to a route+key pair becomes the entity owner. Only the owner (or admin keys) can update that entity. This prevents overwriting others' data. Private zone: The first segment IS your public key (base64url, 43+ chars). Only requests with matching X-Ocean-Key header can read or write. Shared zone: Routes starting with "shared/" are governed by trust drops. A trust drop at trust/{name} with key "acl" defines who can access shared/{name}/*. Trust zone: Routes starting with "trust/" are system-managed. Direct reads are always denied. First write to trust/{name} (key=acl) establishes ownership. Only the owner can update the trust drop. Header: X-Ocean-Key:
-- React when data changes when myapp/orders changes call "https://your-server.com/webhook" -- List webhooks webhooks -- Remove a webhook webhook delete "hook-uuid"Webhook delivery: When a drop is written matching a hook's prefix, the drop is POSTed to the URL. Headers: Content-Type: application/json X-Ocean-Hook-Id:
-- Add to a counter add 1 to pages @home views -- Add a larger amount add 10 to stats @daily visits -- Subtract subtract 2 from inventory @widget stockPure counter updates are extremely fast (<1ms). Counter values are merged into entity reads automatically. 18. Secondary Indexes — Fast entity lookups ─────────────────────────────────────────── Entity lookups use indexes for fast reads. Entity indexes are automatic. Field indexes are explicit.
Indexes are created automatically on frequently-queried fields. Queries filtering on indexed fields are accelerated.
19. GET /health ────────────── { "status": "ok", "active_connections": 456 } 20. VIEW — Serve drops as web pages ──────────────────────────────────── Base URL: https://view.infiniteocean.io Drops become web pages. Write HTML (or CSS, JS, images) to a drop with an entity key, then view it at a clean URL. Update the drop, the URL serves the new version instantly. URL convention: https://view.infiniteocean.io/{route}/{key} https://view.infiniteocean.io/{route}/ ← key defaults to "index" Examples: /mysite/ → route=mysite, key=index /mysite/about → route=mysite, key=about /sites/blog/style.css → route=sites/blog, key=style.css Content-Type detection: 1. payload._content_type (explicit, highest priority) 2. Key extension (.css → text/css, .js → application/javascript, .png → image/png, etc.) 3. Default: text/html; charset=utf-8 Payload conventions: - String payload → served directly as body - Object with _body field → _body is served (allows _content_type alongside) - Object without _body → served as JSON Access control: - Open routes are viewable by anyone - Private/shared routes: pass ?_key=-- Write up to 1000 drops atomically bulk ( save myapp/data @a n: 1, save myapp/data @b n: 2, save other/route "text" )All drops succeed or none do. Max 1000 drops per batch. Each drop validated individually. Access control checked per unique route. Webhooks fire for each drop (non-blocking). SSE subscribers receive each drop. 25. GET /export/:prefix — Export drops in any format ──────────────────────────────────────────────────── Export all drops under a prefix. Default NDJSON, or specify format for CSV, SQL, or MongoDB. Request: GET https://api.infiniteocean.io/export/myapp/data X-Ocean-Key:
-- Query → SQL translate "find users where .role = 'admin' sort .name limit 10" to sql → SELECT * FROM "users" WHERE "role" = 'admin' ORDER BY "name" LIMIT 10; -- Write → SQL (upsert) translate "save users @alice name: 'Alice'" to sql → INSERT INTO "users" ... ON CONFLICT ("_key") DO UPDATE SET ...; -- Delete → SQL translate "remove users @alice" to sql → DELETE FROM "users" WHERE "_key" = 'alice'; -- Any command → curl translate "find users where .active = true" to curl → curl -X POST https://api.infiniteocean.io/query ... -- MySQL dialect translate "find users where .role = 'admin'" to mysql → SELECT * FROM `users` WHERE `role` = 'admin';Targets: sql (PostgreSQL), mysql, curl, rest Commands: find/query, save/write, remove/delete, get/read, search, count 26. Computed Views ─────────────────────────────────────
Saved queries that expand on read. Create a view once, then query it like any route.
-- Create a view (saved query) find users where status = "active" as active_users -- Query through the view find active_users where age > 25
When you query a view, the saved filters are merged with your new filters automatically.
27. run — Serverless Python execution ──────────────────────────────────────-- Store a function save fn/hello @main "def main(ocean, request):\n return {'hello': request.get('name', 'world')}" -- Run a function (sync) run fn/hello name: "Ocean" -- Run async (returns job_id) run fn/process data: "..." asyncFunction requirements: - Payload must be a Python string stored at key "main" - Code must define a callable main(ocean, request) - request is the JSON body from the run command - Return value must be JSON-serializable (dict, list, string, number, bool, None) - print() output is captured in the "stdout" field Ocean client methods (available as first argument): ocean.read(route, key) read route @key ocean.write(route, payload, key=None) write route @key { payload } ocean.query(prefix, filter=None, sort=None, limit=None) query route where ... ocean.search(prefix, text=None, vector=None, top_k=10) search route "text" top N ocean.sql(query) find route where ... ocean.run(route, body=None) run route { body } ocean.upload(route, data, filename) POST /upload/{route} All Ocean client calls use the caller's X-Ocean-Key — normal access control applies. Function isolation (_config entity): Store a _config entity alongside your function to enable code privacy and data isolation. The server auto-injects app_key (your X-Ocean-Key) — it cannot be forged. Fields: execute — "public" = anyone can run (bypasses access check). Omit = default. scopes — Route prefixes the caller's Ocean client can access inside the function. Omit = caller key gets full access. app_key — Auto-injected by server (your X-Ocean-Key). Cannot be set manually. Public functions (execute:"public" + app_key): When a function has execute:"public", anyone can call it — even without an X-Ocean-Key header. The developer's app_key is used for compute billing. Ideal for public APIs, webhooks, and AI agent tools. Two-key model (when _config has app_key): Inside the function, `ocean` uses the developer's key (app state). `ocean.user` uses the caller's key, scoped to declared prefixes. Scoped tokens: When scopes are declared, the caller's key is replaced with a temporary token that only works for the declared route prefixes. Accessing routes outside the scopes returns 403 SCOPE_DENIED. Limits (sync mode): - 10 second timeout (408 EXECUTION_ERROR on timeout) - 256KB max code size - 1MB max stdout capture - Python standard library only (no pip packages) Limits (async mode): - Safety wall-clock: 24 hours Cost: 1 + ceil(ms / 1000) io. Free on your own node. Async retries: &max_retries=N (0-10, default 0). On failure, the job is automatically retried with exponential backoff (10s, 40s, 2m40s, 10m40s, up to 30m cap). No additional io is charged for retries. When all retries are exhausted, the job is marked "abandoned" and a DLQ drop is written to _dlq/runs with the job ID as key. 28. status — Async job status ─────────────────────────────
-- Check status of an async job status "abc-123"Statuses: running — function is executing paused — io balance empty, function paused (resumes when balance is topped up) done — function completed successfully failed — function failed (error field has details) retrying — function failed, waiting for retry (retry_at field has timestamp) abandoned — all retries exhausted, DLQ drop written (dlq: true) Jobs expire after 24 hours. 28b. POST /run/retry/:id — Manually retry a failed job ─────────────────────────────────────────────────────── Retry a failed or abandoned async job. Only the original caller can retry. Charges 1 io for the new execution attempt. Request: POST https://api.infiniteocean.io/run/retry/
-- Run daily at 9am every day at 9am daily-report: run fn/report -- List schedules schedules -- Remove a schedule schedule delete "daily-report"Schedule examples: * * * * * every minute */5 * * * * every 5 minutes 0 * * * * every hour 0 9 * * * daily at 9am 0 9 * * 1 Mondays at 9am 0 0 1 * * first of every month Fields: name — alphanumeric with - or _ (unique job name) route — function route to execute (must have key "main") schedule — standard 5-field cron: minute hour day month weekday body — optional JSON passed as `request` to the function enabled — default true, set false to pause max_retries — 0-5, default 0. On failure, retry with backoff. On exhaustion, DLQ drop written. 33. POST /webhook/:name — Inbound webhook endpoint for integrations ────────────────────────────────────────────────────────────────── Generic inbound endpoint for external services (Stripe, GitHub, Slack, etc.). No Ocean key required — the integration owner's io balance is charged. The raw event is captured as a drop with full headers for signature verification. Setup — Register the integration first: save _integrations @my-stripe type: "stripe" active: true Request — External service sends events here: POST https://api.infiniteocean.io/webhook/my-stripe Content-Type: application/json
-- Opt into paying for a function's compute save fn/my-shop @_billing {} -- Revoke billing (stop paying) delete fn/my-shop @_billingBilling priority for compute charges: 1. _billing.billing_key (explicit consent — payer wrote the entity) 2. _config.app_key (public function — developer pays) 3. requestKey (caller pays — default) The server auto-injects billing_key (the writer's X-Ocean-Key) — cannot be forged. 40. GDPR Compliance — Erasure, Redaction, Retention ──────────────────────────────────────────────────── For apps built on InfiniteOcean that store data about their end-users. Requires write access to the target prefix (X-Ocean-Key header). POST /gdpr/erase — Right to Erasure: POST https://api.infiniteocean.io/gdpr/erase Headers: X-Ocean-Key:
-- Query by date range primitives dates { "$gte": "2026-03-01", "$lt": "2026-04-01" } -- Query by location primitives locations { "$near": { "lat": 55.68, "lon": 12.57, "radius_km": 5 } } -- Query by identifier primitives identifiers { "$has": { "type": "email", "value": "bob@example.com" } } -- Combined query (AND logic) primitives dates { "$gte": "2026-03-01" } amounts { "$gte": 1000, "$unit": "USD" }All filter fields are optional. At least one is required. When multiple filters are provided, results are intersected (AND logic). Date filters: "$gte" — start date >= value (ISO 8601 or YYYY-MM-DD) "$gt" — start date > value "$lte" — start date <= value "$lt" — start date < value Location filters: "$near" — { "lat": number, "lon": number, "radius_km": number } Identifier filters: "$has" — { "type": "email", "value": "a@b.com" } exact type:value match "$has" — "a@b.com" match value across all types "$type" — "drop" all identifiers of this type "$any" — [{ "type": "email", "value": "a@b.com" }, ...] OR match Amount filters: "$gte", "$gt", "$lte", "$lt" — range on value "$unit" — filter by unit (e.g. "USD", "kg"). Default: all units. Access control: results are filtered per-route — you only see entities on routes you have read access to. UNIVERSAL FIELDS — Standardized Payload Fields ──────────────────────────────────────────────── Optional, well-known payload fields that enable cross-route queries. Convention, not enforcement — writes stay schema-free. When present, these fields are automatically indexed. All universal fields use `_` prefix. All are arrays. _texts — Words, messages, thoughts ["Sprint planning notes", "Q3 slides reminder"] _dates — Points and ranges in time [{ "start": "2026-03-15T09:00:00Z", "end": "2026-03-15T10:30:00Z", "tz": "Europe/Copenhagen", "label": "meeting" }] Fields: start (required), end, tz, label _identifiers — References to anything [{ "type": "email", "value": "alice@example.com", "name": "Alice", "role": "organizer" }, { "type": "github", "value": "octocat" }, { "type": "drop", "value": "work/meetings\0q3-review", "label": "related" }] Fields: type (required), value (required), name, role, label Common types: email, tel, key, drop, github, domain, iban, eik, url _blobs — Files and attachments [{ "url": "/blob/abc123", "mime": "image/png", "name": "screenshot.png", "size": 245760 }] Fields: url (required), mime (required), name, size _locations — Coordinates [{ "lat": 55.6761, "lon": 12.5683, "name": "Copenhagen" }] Fields: lat (required), lon (required), name, address, city, country _amounts — Quantities and money [{ "value": 1500, "unit": "USD", "label": "total" }, { "value": 2.3, "unit": "kg", "label": "weight" }] Fields: value (required), unit, label Full example:
-- Complex nested payloads use JSON syntax (always supported) save work/meetings @q3-review { "title": "Q3 Budget Review", "_texts": ["Quarterly budget review with marketing"], "_dates": [{"start": "2026-03-15T09:00:00Z", "tz": "Europe/Copenhagen"}], "_identifiers": [ {"type": "email", "value": "alice@example.com", "role": "organizer"}, {"type": "email", "value": "bob@example.com", "role": "attendee"} ], "_locations": [{"lat": 55.67, "lon": 12.56, "name": "HQ"}], "_amounts": [{"value": 250000, "unit": "USD", "label": "budget"}], "_blobs": [{"url": "/blob/q3", "mime": "application/pdf"}] }This drop is now queryable via primitives by date, location, identifier, amount, or any combination — across all routes. 42. mail — Email as Drops ═════════════════════════ Full email infrastructure built into InfiniteOcean. Claim a subdomain mailbox (username@username.infiniteocean.io), add custom domains, send and receive email — all stored as drops with universal fields.
-- Claim a subdomain mailbox (100 io) mail claim "alice" -- Send an email (1 io) mail to "bob@example.com" subject "Hello" body "Hello from the ocean" -- Add a custom domain (500 io) mail domain add "mycoolsite.com"Mail storage routes: mail/{username}/sent — outbound emails (one drop per email) mail/{username}/inbound — received emails (one drop per email) mail/domains — domain ownership records Every email drop includes universal fields (_texts, _dates, _identifiers) so emails are queryable via primitives and cross-route search. Username rules: 3-30 chars, a-z 0-9 and hyphens. Must not start/end with hyphen. Sending rules: from: required — must be an address on a verified domain you own to: required — recipient email (string or array) subject: optional — defaults to "(no subject)" html: optional — HTML body text: optional — plain text body (fallback) reply_to: optional — reply-to address cc/bcc: optional — CC/BCC recipients Custom domain DNS: POST /mail/domain returns DNS records to add at your registrar. After adding records, call POST /mail/domain/verify to confirm. GET /mail/my-domains — List your mail domains (HTTP-only). Query your inbox: find mail/alice/inbound latest sort created_at desc limit 20 Search your mail: search mail/alice/inbound "invoice" top 10 Find emails across all routes by sender: primitives identifiers { "$has": { "type": "email", "value": "bob@example.com" } } Inbound email (POST /mail/inbound, POST /mail/events): These are internal webhook endpoints called by Resend — not user-facing. Inbound emails arrive as drops in mail/{username}/inbound. 43. push — Web Push Notifications ══════════════════════════════════ Native Web Push (RFC 8292 VAPID + RFC 8291 encryption) built into InfiniteOcean. Free — no io charge for push notifications.
-- Send a push notification to users push "New message" to ("ocean-key-1", "ocean-key-2") body "You have a new message" -- Subscribe to push on route drops push subscribe "myapp/orders" -- List push subscriptions push subscriptionsGET /push/vapid-key — Get VAPID public key (HTTP-only, needed by browser Push API): Returns: { "publicKey": "base64url-encoded-P256-public-key" } The client uses this key with PushManager.subscribe(): const reg = await navigator.serviceWorker.ready; const sub = await reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(publicKey) }); POST /push/subscribe — Register a browser push subscription (HTTP-only): Body: { "endpoint": "https://...", "keys": { "p256dh": "...", "auth": "..." } } Returns: { "subscription_id": "uuid" } DELETE /push/subscribe/:id — Remove a push subscription (HTTP-only) Route subscriptions: Subscribe to push notifications whenever a drop is written to a matching route. Works like webhooks but delivers to browser push instead of HTTP POST. POST /push/route-subscribe — Subscribe to route drops (HTTP-only): Body: { "prefix": "myapp/orders" } DELETE /push/route-subscribe/:id — Remove route subscription (HTTP-only) GET /push/route-subscriptions — List route subscriptions (HTTP-only) Expired subscriptions (HTTP 410) are automatically cleaned up. 44. values — Custom currencies & tokens ══════════════════════════════════════════ Create your own currency, mint tokens, transfer between keys with 0.1% platform fee.
-- Create a currency value create "gold" name "Gold Coins" symbol "GLD" max_supply 1000000 decimals 2
-- Mint tokens (creator only) value mint "gold" 1000 value mint "gold" 500 to "recipientKey"
-- Transfer (0.1% fee paid by sender on top) value transfer "gold" 100 to "recipientKey"
-- Burn tokens from your balance value burn "gold" 50
-- Read operations value balance "gold" value balance "gold" holder "otherKey" value supply "gold" value currencies value holders "gold" value ledger "gold"HTTP endpoints: POST /values/create — Create currency (id, name, symbol, scope, max_supply, decimals) POST /values/mint — Mint tokens (currency, amount, to) POST /values/transfer — Transfer tokens (currency, amount, to) — 0.1% fee POST /values/burn — Burn tokens (currency, amount) GET /values/balance?currency=X&holder=Y — Check balance GET /values/currencies?creator=X — List currencies GET /values/currency/:id — Currency details GET /values/holders?currency=X — List holders GET /values/supply?currency=X — Total and max supply GET /values/ledger/:id?limit=N — Transaction history Scope: "public" (default, anyone can receive) or "private" (only existing holders or creator). Transfer fee: 0.1% platform fee. Sender pays amount + fee, recipient gets exact amount. 45. list — List entity keys ────────────────────────────
-- List all entity keys on a route list usersReturns entity keys for the given route. 45. context — Get project context ───────────────────────────────────
-- Get full project context (architecture, schemas, memory) contextReturns agent context including architecture decisions, code patterns, bug history, schemas, and lessons from all previous sessions. 46. event / events / rsvp — Calendar ──────────────────────────────────────
-- Create a calendar event event work/meetings @standup title "Daily Standup" start "2026-03-15T09:00:00Z" -- List events on a route events work/meetings -- RSVP to an event rsvp work/meetings @standup accepted -- Get calendar feed URL calendar feed "work/meetings"ICS feeds, invite emails, and RSVP built into InfiniteOcean. Any entity with start+title fields is treated as an event. Feed tokens at _calendar/tokens. 47. subkey — Sub-key management ────────────────────────────────
-- Create a sub-key with restricted permissions subkey create label: "read-only" permissions: ("read", "search") -- List sub-keys subkey list -- Revoke a sub-key subkey "sub-key-id" revoke48. GET /listen/:route — Continuous binary stream ────────────────────────────────────────────────── Stream raw binary data from blob drops on a route as a continuous HTTP stream. Designed for audio, video, or any binary pipeline — works as a direct `