On this page

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

  1. Apply for a client. The operator approves it.
  2. In the console, create a key with the scopes you need and copy it (shown once).
  3. 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.

ScopeGrants
worlds:readRead the world catalogue and each world's capabilities.
tools:readRead the tool registry (names, worlds, input schemas).
tools:invokeInvoke a tool against a world.
events:readRead and poll events.
events:subscribeCreate and delete webhook subscriptions.
data:readRead 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.

WorldKeyDomainToolsWrite tools
WUUUHwuuuhwuuuh.com31
AI SENATUSaisenatusaisenatus.com41
HelpBLESShelpblesshelpbless.com41
TaskFamilytaskfamilytaskfamily.com41
6on7sixonseven6on7.com31
Introworthintroworthintroworth.com41
Soclasicasoclasicasoclasica.com41
StoryChoosestorychoosestorychoose.com41
AstroDIOSastrodiosastrodios.com41

Catalogue

GET/api/platform/v1/no key

Discovery: version, scopes, endpoint list. No credential.

curl -s https://onelifevibe.com/api/platform/v1/
GET/api/platform/v1/healthno key

Liveness plus which dependencies are configured (no secrets).

GET/api/platform/v1/worldsworlds:read

The worlds this key may use, with each one’s client id and tool names.

GET/api/platform/v1/toolstools:read

The registry: name, world, class, input JSON Schema. Tools outside the key’s worlds are omitted.

Invoke a tool

POST/api/platform/v1/tools/:nametools:invoke

Runs one tool. Arguments are validated against its schema before anything is called.

Body fieldTypeMeaning
argumentsobjectThe tool’s declared inputs (see registry).
subjectstring?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

GET/api/platform/v1/eventsevents:read

Cursor pagination. Pass the previous response’s cursor as after.

QueryMeaning
afterSequence cursor; omit for the beginning.
limit1–100, default 25.
worldOnly this world.
typeOnly 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}
GET/api/platform/v1/events/:idevents:read

One event by id. 404 when it is outside your worlds.

Event typeWhen
tool.invokedA tool ran for this client (platform) or through the assistant gateway. Carries tool name, status and duration.
client.approvedThe operator approved this client. Plan and status are in data.
client.suspendedThe operator suspended this client. Keys stop working until reinstated.
subscription.changedStripe reported a subscription change. Carries plan, status and period end.
world.eventA world published an event through POST /ingest; data.topic names it.

Subscriptions

POST/api/platform/v1/subscriptionsevents:subscribe

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.

GET/api/platform/v1/subscriptionsevents:read

Your endpoints with health: last delivery, failures, disabled state.

DELETE/api/platform/v1/subscriptions/:idevents:subscribe

Deletes the endpoint and stops deliveries. Returns 204.

GET/api/platform/v1/subscriptions/:id/deliveriesevents:read

Per-delivery status, attempts and the response code.

Ingest (worlds)

POST/api/platform/v1/ingestno key

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:

HeaderValue
webhook-idThe event id. Dedupe on this: delivery is at-least-once.
webhook-timestampUnix seconds. Reject anything more than 5 minutes away.
webhook-signaturev1 = HMAC-SHA256 of {timestamp}.{raw body} with your endpoint secret.
x-olv-event / x-olv-worldConvenience 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.

ToolWorldClassInputs
senatus_get_my_accountaisenatus.comRead–
senatus_list_my_submissionsaisenatus.comReadlimit? integer 1–50
senatus_list_my_followsaisenatus.comRead–
senatus_followaisenatus.comWritekind senator | category | topic | tribunal_case
ref string ≤120
follow boolean
wuuuh_get_dashboardwuuuh.comRead–
wuuuh_list_action_planwuuuh.comReadstatus? todo | doing | done | skipped
limit? integer 1–50
wuuuh_set_action_item_statuswuuuh.comWriteitem_id string ≤80
status todo | doing | done | skipped
helpbless_list_my_needshelpbless.comReadlimit? integer 1–50
helpbless_list_my_giftshelpbless.comReadlimit? integer 1–50
helpbless_list_saved_needshelpbless.comReadlimit? integer 1–50
helpbless_save_needhelpbless.comWriteneed_id string ≤80
saved boolean
taskfamily_list_taskstaskfamily.comReaddate? YYYY-MM-DD
days? integer 1–7
taskfamily_list_pending_approvalstaskfamily.comRead–
taskfamily_list_upcoming_eventstaskfamily.comReaddays? integer 1–30
taskfamily_add_eventtaskfamily.comSensitive writetitle string ≤120
starts_at ISO 8601 date-time
ends_at? ISO 8601 date-time
all_day? boolean
notes? string ≤500
idempotency_key? string ≤80
sixonseven_list_notifications6on7.comReadunread_only? boolean
limit? integer 1–50
sixonseven_get_verification_status6on7.comRead–
sixonseven_mark_notifications_read6on7.comWritenotification_id? string ≤80
introworth_get_inbox_summaryintroworth.comRead–
introworth_get_earnings_summaryintroworth.comRead–
introworth_list_my_intentsintroworth.comRead–
introworth_close_intentintroworth.comWriteintent_id string ≤80
soclasica_search_productssoclasica.comReadquery string ≤80
limit? integer 1–50
soclasica_list_my_orderssoclasica.comReadlimit? integer 1–50
soclasica_get_order_statussoclasica.comReadorder_number string ≤80
soclasica_set_marketing_emailssoclasica.comWritesubscribed boolean
storychoose_list_storiesstorychoose.comReadlimit? integer 1–50
storychoose_get_storystorychoose.comReadstory_id string ≤80
storychoose_list_charactersstorychoose.comRead–
storychoose_favorite_storystorychoose.comWritestory_id string ≤80
favorite boolean
astrodios_get_todayastrodios.comReaddate? YYYY-MM-DD
astrodios_scan_daysastrodios.comReaddays? integer 1–30
astrodios_get_planastrodios.comRead–
astrodios_set_morning_emailastrodios.comWriteenabled boolean
hour? integer 0–23

Errors

Every error is JSON: {"error":{"code":"…","message":"…","hint":"…"}}. Codes are stable; match on code, never on the message.

HTTPCodeWhat to do
400invalid_json, invalid_url, invalid_topic, invalid_world, payload_too_largeFix the request.
401missing_credentials, invalid_credentials, key_revoked, key_expiredCreate or rotate a key in the console.
403client_pending, client_suspended, client_rejected, insufficient_scope, world_not_allowed, consent_requiredApproval, scope, or the person’s consent is missing.
404unknown_tool, not_foundCheck GET /tools.
409endpoint_limit, key_limitDelete one or upgrade the plan.
429rate_limited, quota_exceededHonour Retry-After; quota resets at 00:00 UTC.
502world_unreachable, world_<status>The world failed the call; retry after checking state.
503unavailableTemporarily misconfigured; contact support.

Limits and plans

PlanPriceRateEvents/dayEndpointsKeys
CommunityFree60/min5,00023
Growth$49/month300/min100,0001010
Scale$249/month1200/min1,000,0005025

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.