OneLifeVibe Platform
Developer Documentation
API v1 · last updated 25 October 2026 · OpenAPI 3.1
Overview
The Platform API is one credential for 9 independent websites. It is a curated, versioned surface,
not a proxy: you can call the 34 listed tools and read the event log, and nothing else on those
sites is reachable with a platform key. Every event the platform observes is both retrievable
(GET /events) and pushable (webhooks), so polling and push return the same
records.
Two directions:
- Inbound — you call the API with an
olvp_…key. - Outbound — the platform POSTs signed events to your HTTPS endpoint. Each world can also publish its own events through ingest, which fans out to every subscriber.
Quick start
- Apply for a client. The operator approves it.
- In the console, create a key with the scopes you need and copy it (shown once).
- Call the API.
export OLV_KEY="olvp_abc123_your32characterSecret"
# What can this key see?
curl -s https://onelifevibe.com/api/platform/v1/worlds -H "Authorization: Bearer $OLV_KEY"
# Which tools exist, with their JSON Schema?
curl -s https://onelifevibe.com/api/platform/v1/tools -H "Authorization: Bearer $OLV_KEY"
# Run one
curl -s https://onelifevibe.com/api/platform/v1/tools/soclasica_search_products \
-H "Authorization: Bearer $OLV_KEY" -H "Content-Type: application/json" \
-d '{"arguments":{"query":"serum","limit":5}}'
# Read events, then poll from a cursor
curl -s "https://onelifevibe.com/api/platform/v1/events?limit=25" -H "Authorization: Bearer $OLV_KEY"
Authentication
Send the key as Authorization: Bearer olvp_… or as X-API-Key: olvp_…. Keys are
random 32-character secrets; the platform stores only a SHA-256 hash, so a key cannot be recovered — rotate by
creating a new one and revoking the old. A key carries a public prefix (olvp_abc123_) that appears in
the console so you can tell keys apart without storing the secret.
Key state is checked on every call, in this order: shape → existence → hash → expiry → revocation → client
status. A client that is not approved gets 403 client_pending /
client_suspended even if the key itself is valid.
Platform keys act as the client. To act as a person, pass subject on a tool call, and only for a
person who approved your client — otherwise the answer is 403 consent_required. Console sessions are
a separate thing entirely (an HttpOnly cookie plus a CSRF header) and cannot be used against the API.
Scopes and worlds
A scope decides what a key may do; its world list decides where. Both must allow the call.
| Scope | Grants |
|---|---|
worlds:read | Read the world catalogue and each world's capabilities. |
tools:read | Read the tool registry (names, worlds, input schemas). |
tools:invoke | Invoke a tool against a world. |
events:read | Read and poll events. |
events:subscribe | Create and delete webhook subscriptions. |
data:read | Read aggregate, non-personal network statistics. |
Worlds: ["*"] or a list such as ["wuuuh","soclasica"]. A key limited to WUUUH gets
403 world_not_allowed on a Soclasica tool even when it holds tools:invoke.
| World | Key | Domain | Tools | Write tools |
|---|---|---|---|---|
| WUUUH | wuuuh | wuuuh.com | 3 | 1 |
| AI SENATUS | aisenatus | aisenatus.com | 4 | 1 |
| HelpBLESS | helpbless | helpbless.com | 4 | 1 |
| TaskFamily | taskfamily | taskfamily.com | 4 | 1 |
| 6on7 | sixonseven | 6on7.com | 3 | 1 |
| Introworth | introworth | introworth.com | 4 | 1 |
| Soclasica | soclasica | soclasica.com | 4 | 1 |
| StoryChoose | storychoose | storychoose.com | 4 | 1 |
| AstroDIOS | astrodios | astrodios.com | 4 | 1 |
Catalogue
Discovery: version, scopes, endpoint list. No credential.
curl -s https://onelifevibe.com/api/platform/v1/
Liveness plus which dependencies are configured (no secrets).
The worlds this key may use, with each one’s client id and tool names.
The registry: name, world, class, input JSON Schema. Tools outside the key’s worlds are omitted.
Invoke a tool
Runs one tool. Arguments are validated against its schema before anything is called.
| Body field | Type | Meaning |
|---|---|---|
arguments | object | The tool’s declared inputs (see registry). |
subject | string? | A person who approved your client. Omit to act as the client. |
{"ok":true,"world":"soclasica","tool":"soclasica_search_products","result":{…},"ms":412}
Every invocation is written to the event log as tool.invoked, so a subscriber sees writes and
reads without polling.
Events
Cursor pagination. Pass the previous response’s cursor as after.
| Query | Meaning |
|---|---|
after | Sequence cursor; omit for the beginning. |
limit | 1–100, default 25. |
world | Only this world. |
type | Only this event type. |
{"events":[{"id":"evt_…","type":"tool.invoked","world":"soclasica","subject":null,
"origin":"platform","created_at":"2026-10-25T09:14:03.512Z","data":{"tool":"…","status":200,"ms":412}}],
"cursor":1843,"has_more":false}
One event by id. 404 when it is outside your worlds.
| Event type | When |
|---|---|
tool.invoked | A tool ran for this client (platform) or through the assistant gateway. Carries tool name, status and duration. |
client.approved | The operator approved this client. Plan and status are in data. |
client.suspended | The operator suspended this client. Keys stop working until reinstated. |
subscription.changed | Stripe reported a subscription change. Carries plan, status and period end. |
world.event | A world published an event through POST /ingest; data.topic names it. |
Subscriptions
Registers an HTTPS endpoint. The signing secret is returned once, in this response only.
{"url":"https://api.example.com/onelifevibe","topics":["tool.invoked","world.*"],"worlds":["*"]}
Only https on port 443, public hostnames (no loopback, private ranges or bare IPs), and no
credentials in the URL. A subscription may name exact topics or a namespace wildcard that covers a real type.
Your endpoints with health: last delivery, failures, disabled state.
Deletes the endpoint and stops deliveries. Returns 204.
Per-delivery status, attempts and the response code.
Ingest (worlds)
A world publishes its own event. Authenticated with that world’s ingest key, never a client key.
{"topic":"order.placed","subject":"member:3f1c…","data":{"order":"SC-1042","total":8400}}
The world is taken from the key, so a world can never publish as another. topic must
look like order.placed; data is capped at 32 KB. The
response is 202 with the event id and how many endpoints it was queued for.
Webhooks
Each delivery is a POST with these headers:
| Header | Value |
|---|---|
webhook-id | The event id. Dedupe on this: delivery is at-least-once. |
webhook-timestamp | Unix seconds. Reject anything more than 5 minutes away. |
webhook-signature | v1 = HMAC-SHA256 of {timestamp}.{raw body} with your endpoint secret. |
x-olv-event / x-olv-world | Convenience copies of the body’s type and world. |
Verify against the raw body, before parsing and in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, headers, secret) {
const t = Number(headers['webhook-timestamp']);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) throw new Error('stale webhook');
const expected = 'v1=' + createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected), b = Buffer.from(String(headers['webhook-signature'] || ''));
if (a.length !== b.length || !timingSafeEqual(a, b)) throw new Error('bad signature');
return JSON.parse(rawBody); // then dedupe on event.id
}
Python equivalent:
import hmac, hashlib, time, json
def verify(raw_body: bytes, headers, secret: str) -> dict:
t = int(headers["webhook-timestamp"])
if abs(time.time() - t) > 300:
raise ValueError("stale webhook")
expected = "v1=" + hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, headers["webhook-signature"]):
raise ValueError("bad signature")
return json.loads(raw_body)
Respond 2xx quickly (flush the body first); anything else is a failure. Body:
{"id":"evt_…","type":"world.event","world":"soclasica","subject":"member:3f1c…",
"origin":"ingest","created_at":"2026-10-25T09:14:03.512Z",
"data":{"topic":"order.placed","order":"SC-1042"},
"delivery":{"endpoint_id":"whe_…","attempts":1}}
Retries
Deliveries are retried after 30s, 2m, 10m, 1h, 6h
— 6 attempts in total — then marked dead and visible in the console and in
GET /subscriptions/:id/deliveries. A 404 or 410 disables the endpoint after
three consecutive failures. Redirects are never followed (a signature must not travel to a new host), requests time
out after 10 s, and only the first 512 bytes of your
response are kept for diagnosis.
Tool registry
These are the tools a platform key can invoke. Read tools change nothing; write tools change only that
person’s own data. Inputs marked ? are optional.
| Tool | World | Class | Inputs |
|---|---|---|---|
senatus_get_my_account | aisenatus.com | Read | – |
senatus_list_my_submissions | aisenatus.com | Read | limit? integer 1–50 |
senatus_list_my_follows | aisenatus.com | Read | – |
senatus_follow | aisenatus.com | Write | kind senator | category | topic | tribunal_caseref string ≤120follow boolean |
wuuuh_get_dashboard | wuuuh.com | Read | – |
wuuuh_list_action_plan | wuuuh.com | Read | status? todo | doing | done | skippedlimit? integer 1–50 |
wuuuh_set_action_item_status | wuuuh.com | Write | item_id string ≤80status todo | doing | done | skipped |
helpbless_list_my_needs | helpbless.com | Read | limit? integer 1–50 |
helpbless_list_my_gifts | helpbless.com | Read | limit? integer 1–50 |
helpbless_list_saved_needs | helpbless.com | Read | limit? integer 1–50 |
helpbless_save_need | helpbless.com | Write | need_id string ≤80saved boolean |
taskfamily_list_tasks | taskfamily.com | Read | date? YYYY-MM-DDdays? integer 1–7 |
taskfamily_list_pending_approvals | taskfamily.com | Read | – |
taskfamily_list_upcoming_events | taskfamily.com | Read | days? integer 1–30 |
taskfamily_add_event | taskfamily.com | Sensitive write | title string ≤120starts_at ISO 8601 date-timeends_at? ISO 8601 date-timeall_day? booleannotes? string ≤500idempotency_key? string ≤80 |
sixonseven_list_notifications | 6on7.com | Read | unread_only? booleanlimit? integer 1–50 |
sixonseven_get_verification_status | 6on7.com | Read | – |
sixonseven_mark_notifications_read | 6on7.com | Write | notification_id? string ≤80 |
introworth_get_inbox_summary | introworth.com | Read | – |
introworth_get_earnings_summary | introworth.com | Read | – |
introworth_list_my_intents | introworth.com | Read | – |
introworth_close_intent | introworth.com | Write | intent_id string ≤80 |
soclasica_search_products | soclasica.com | Read | query string ≤80limit? integer 1–50 |
soclasica_list_my_orders | soclasica.com | Read | limit? integer 1–50 |
soclasica_get_order_status | soclasica.com | Read | order_number string ≤80 |
soclasica_set_marketing_emails | soclasica.com | Write | subscribed boolean |
storychoose_list_stories | storychoose.com | Read | limit? integer 1–50 |
storychoose_get_story | storychoose.com | Read | story_id string ≤80 |
storychoose_list_characters | storychoose.com | Read | – |
storychoose_favorite_story | storychoose.com | Write | story_id string ≤80favorite boolean |
astrodios_get_today | astrodios.com | Read | date? YYYY-MM-DD |
astrodios_scan_days | astrodios.com | Read | days? integer 1–30 |
astrodios_get_plan | astrodios.com | Read | – |
astrodios_set_morning_email | astrodios.com | Write | enabled booleanhour? integer 0–23 |
Errors
Every error is JSON: {"error":{"code":"…","message":"…","hint":"…"}}. Codes are stable; match on
code, never on the message.
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_json, invalid_url, invalid_topic, invalid_world, payload_too_large | Fix the request. |
| 401 | missing_credentials, invalid_credentials, key_revoked, key_expired | Create or rotate a key in the console. |
| 403 | client_pending, client_suspended, client_rejected, insufficient_scope, world_not_allowed, consent_required | Approval, scope, or the person’s consent is missing. |
| 404 | unknown_tool, not_found | Check GET /tools. |
| 409 | endpoint_limit, key_limit | Delete one or upgrade the plan. |
| 429 | rate_limited, quota_exceeded | Honour Retry-After; quota resets at 00:00 UTC. |
| 502 | world_unreachable, world_<status> | The world failed the call; retry after checking state. |
| 503 | unavailable | Temporarily misconfigured; contact support. |
Limits and plans
| Plan | Price | Rate | Events/day | Endpoints | Keys |
|---|---|---|---|---|---|
| Community | Free | 60/min | 5,000 | 2 | 3 |
| Growth | $49/month | 300/min | 100,000 | 10 | 10 |
| Scale | $249/month | 1200/min | 1,000,000 | 50 | 25 |
A call gets 429 rate_limited with X-RateLimit-Limit and
X-RateLimit-Remaining when the per-minute allowance is used, and 429 quota_exceeded with
Retry-After when the daily allowance is. Bodies are limited to 64 KB; events
to 32 KB.
Console and approvals
Clients are approved by an operator. Until then a client can sign in, inspect limits and prepare, but its keys do not work and it receives no events. The console is at /platform/: keys, webhook endpoints with a test button, the last 30 days of usage, delivery history and Stripe billing.
Operator actions use PLATFORM_OPERATOR_TOKEN (or the older GLOBAL_ADMIN_TOKEN) against /api/platform/v1/admin/clients:
curl -s -X POST https://onelifevibe.com/api/platform/v1/admin/clients/<id>/approve \
-H "Authorization: Bearer $PLATFORM_OPERATOR_TOKEN" -H "Content-Type: application/json" \
-d '{"plan":"community"}'
# …/reject, …/suspend, …/reinstate are identical in shape.
OpenAPI
The machine-readable description is generated from the same registries as this page:
/api/platform/v1/openapi.json. Import it into Postman,
Insomnia or your SDK generator.
Questions: support@onelifevibe.com.