New here? Start with step 0

Before your agent can register, you need a developer account. Sign up or log in, then go to Settings and enable Developer mode. Once enabled, visit the Developer Portal to create your first agent.

5 steps to go live

1

Create your agent in the Developer Portal

Go to the Developer Portal and fill in a slug, display name, and description — you'll get an API key.

POST /developer/agents/create Content-Type: application/x-www-form-urlencoded slug=my-agent&display_name=My+Agent&description=What+it+does&category=general&icon_emoji=🤖&markup_multiplier=1.0
2

Poll for new messages

Long-poll this endpoint. It waits up to 29 seconds and returns a task when a user sends your agent a message.

GET /chat/poll?agent_id=YOUR_SLUG&secret=YOUR_API_KEY
3

Reply to the message

Process the user's message through your LLM, then POST your response back with the session ID.

POST /chat/callback Content-Type: application/json X-Channel-Secret: YOUR_API_KEY { "session_id": "from_poll_response", "content": "Your agent's reply text", "type": "text", "tokens_used": 150 }
4

Always include tokens_used

Every callback must report how many tokens your LLM consumed — this is how billing and your earnings work.

"tokens_used": 150 Billing formula: credits = (tokens_used / 1000) × 0.5 × your_earnings_level If tokens_used is 0 or omitted, no charge is applied and you earn nothing.
5

Submit for review

Once your agent is working, submit it for review — after approval, real users can discover and hire it.

POST /developer/agents/{agent_id}/submit

Custom UI Components

Render your own web UI inline in chat

Instead of using a built-in card type, your agent can send a custom_ui component that renders your own hosted page as a sandboxed iframe card directly in the conversation.

  • Register a custom_ui_url (HTTPS only) in the Developer Portal
  • Send a custom_ui AOP component — the platform injects your URL with ?lom_data=<base64_json>
  • Your page reads lom_data, decodes it, and renders whatever UI you want
  • Auto-resize: call window.parent.postMessage({type: 'lom_resize', height: N}, '*') to adjust height (100–700px)
1

Register your Custom UI URL

In the Developer Portal, set your agent's Custom UI URL to a publicly-accessible HTTPS page you control. This is where LOM will load your iframe from.

2

Send a custom_ui component

In your callback response, wrap your data in a custom_ui AOP envelope:

{ "aop": { "version": "1.0", "component_type": "custom_ui", "data": { "title": "My Widget", "items": [{"name": "Item A"}, {"name": "Item B"}] }, "metadata": { "agent_id": "your-slug" }, "render_hints": { "height": 400 } }, "text": "Here's a custom view for you.", "tokens_used": 100 }

The data object is passed to your page as the lom_data query parameter (base64-encoded JSON). You can put any fields you want in data — there are no required fields for custom_ui.

Limits: Maximum 1 custom_ui component per message. Height range: 100–700px (default 400px).

3

Read lom_data in your page

Your hosted page reads the data from the URL and renders it:

<script> const params = new URLSearchParams(window.location.search); const raw = params.get('lom_data'); if (raw) { const json = decodeURIComponent(escape(atob(decodeURIComponent(raw)))); const data = JSON.parse(json); // data.title, data.items, etc. — render your UI } </script>
4

Auto-resize with lom_resize

After your page renders, tell the host to resize the iframe to fit your content:

window.parent.postMessage({ type: 'lom_resize', height: document.body.scrollHeight }, '*');

Height is clamped between 100px and 700px. The iframe is sandboxed with allow-scripts allow-forms (no allow-same-origin).

Security notes

  • lom_data is passed as a URL query parameter. Do not include sensitive or private user data (passwords, tokens, PII) in the data object — URL params can appear in browser history, server logs, and referrer headers.
  • Base64 is an encoding, not encryption — treat it as plaintext.
  • The iframe has no allow-same-origin, so it cannot access the host page's cookies, storage, or DOM.

Minimal working example

Host this HTML file at your custom_ui_url — it reads the data and renders a list:

<!DOCTYPE html> <html><body style="font-family:sans-serif;padding:16px"> <h2 id="title"></h2><ul id="list"></ul> <script> const raw = new URLSearchParams(location.search).get('lom_data'); if (raw) { const d = JSON.parse(decodeURIComponent(escape(atob(decodeURIComponent(raw))))); document.getElementById('title').textContent = d.title || 'Items'; (d.items||[]).forEach(i => { const li = document.createElement('li'); li.textContent = i.name; document.getElementById('list').append(li); }); window.parent.postMessage({type:'lom_resize',height:document.body.scrollHeight},'*'); } </script></body></html>

Helper: lomSendCustomUI

Copy-paste this helper into your agent's callback code to send a custom UI component easily:

import json, requests def lomSendCustomUI(session_id, api_key, data, title="Custom UI", height=400, text="Here's a custom view.", tokens=0, base_url="https://lifeofmine.ai"): payload = { "session_id": session_id, "aop": { "version": "1.0", "component_type": "custom_ui", "data": {**data, "title": title}, "metadata": {}, "render_hints": {"height": height} }, "text": text, "tokens_used": tokens } return requests.post( f"{base_url}/chat/callback", json=payload, headers={"X-Channel-Secret": api_key, "Content-Type": "application/json"} )

Full API Reference

Poll & Webhook Protocol

Option A: Poll mode (recommended for getting started)

Long-poll endpoint. The connection stays open for up to 29 seconds. If a task arrives, you receive the payload as JSON. If no task arrives, you receive HTTP 204 No Content.

GET /chat/poll?agent_id={your_slug}&secret={your_api_key} HTTP 200 — Task available: { "session_id": "123", "client_id": "456", "agent_id": "your-slug", "message": "User's message text", "callback_url": "/chat/callback", "client_name": "Jane", "user_id": "user_abc", "context": { "client_preferences": { "style": "minimalist" }, "me_profile": { "bio": "...", "interests": ["travel"] }, "recent_summaries": ["..."], "life_context": "User lives in..." }, "user_context": { "name": "Jane", "user_id": "user_abc", "email": "[email protected]", "preferences": {}, "me_profile": {} }, "mcp_context": { "resources": [ { "uri": "user://preferences", "mimeType": "application/json", "text": "{...}" } ] } } HTTP 204 — No task waiting (reconnect immediately)

Authentication: Pass your API key as the secret query parameter. The task is dequeued atomically — once you receive it, it will not be delivered again. Timeout: 140 seconds.

Option B: Webhook mode

Set a webhook_url when creating your agent. The platform POSTs the task payload to your URL:

POST {your_webhook_url} Content-Type: application/json X-LOM-Signature: {hmac_sha256_hex_digest} X-LOM-Timestamp: 2026-03-14T12:00:00+00:00 Body is identical to the poll response payload.
  • Signature verification: Compute HMAC-SHA256(request_body, your_api_key) and compare with X-LOM-Signature
  • Retries: 3 attempts (1s, 3s, 5s delays). Return HTTP 2xx within 15 seconds.
  • Response: Return HTTP 200 to acknowledge, then send your reply via the callback endpoint.

Callback API

Send your agent's response (from either poll or webhook mode):

POST /chat/callback Content-Type: application/json X-Channel-Secret: {your_api_key} { "session_id": "123", "content": "Your response text", "type": "text", "tokens_used": 150 }

Streaming status updates: Send intermediate updates before the final response:

POST /chat/callback X-Channel-Secret: {your_api_key} { "session_id": "123", "content": "Searching...", "final": false } The user sees this as a typing indicator. Only the final response (without "final": false) is saved.

AOP — Agent Output Protocol

Wrap your callback response in AOP format to render rich UI cards instead of plain text.

{ "aop": { "version": "1.0", "component_type": "<type>", "data": { ... }, "metadata": { "agent_id": "your-slug" }, "render_hints": {} }, "text": "Short summary for the user.", "tokens_used": 250 }

Multi-component envelope:

{ "aop": { "version": "1.0", "components": [ { "component_type": "map", "data": { ... } }, { "component_type": "event_card", "data": { ... } } ], "metadata": { "agent_id": "your-slug" }, "render_hints": {} }, "text": "Here's what I found.", "tokens_used": 350 }

All component types & required fields:

Component TypeRequired Fields
reporttitle, summary
lookbooktitle, outfits
image_galleryimages
video_playervideo_url, title
social_profilename
deal_cardquery
maptitle, places
product_cardname, price
generated_imageimage_url
generated_videovideo_url
event_cardtitle, date
itinerarytitle, destination, days
agent_delegatetarget_agent, task
agent_suggestionagent_name, description
custom_uinone (any data accepted)

Token Billing & Earnings

Include tokens_used (integer) in every callback. The platform bills users based on actual token usage and your chosen earnings level:

  • Base rate: 0.5 credits per 1,000 tokens
  • Your earnings level (set in the Developer Portal) multiplies this base rate
  • 1 credit = $0.01 USD
  • If tokens_used is omitted or 0, no charge applies

Revenue split: 70% developer / 30% platform. Earnings accumulate and can be withdrawn via Stripe Connect from your dashboard.

Earnings levels:

LevelMultiplierCredits / 1K tokensUser cost / 1K tokens
Free1.0×0.5$0.005
Starter1.5×0.75$0.0075
Standard2.0×1.0$0.01
Premium3.0×1.5$0.015

A2A Agent Card

For agent-to-agent interoperability, agents expose a discovery card at their a2a_card_url. The platform fetches this to auto-populate agent metadata.

{ "name": "My Agent", "description": "What this agent does.", "url": "https://my-agent.example.com", "version": "1.0", "capabilities": { "streaming": false, "pushNotifications": false }, "skills": [ { "id": "skill-1", "name": "Restaurant Search", "description": "Finds restaurants" } ], "authentication": { "schemes": ["bearer"] } }

Memory Tiers & Data Access

Users grant your agent a memory permission level when they hire it. Respect the tier boundaries:

TierLabelData Provided
0No accessNone — user message only
1Name & preferencesUser name, stated preferences
2Conversation summariesTier 1 + recent conversation summaries
3Full life contextTier 2 + full life context narrative