
Linq Message Apps
Add Connect Onairos to Linq-powered message apps: the onairos-text-auth SDK for backends, the onairos-text-auth-mcp tools for LLM agents, and the raw HTTP contract underneath.
Message app integration
Connect Onairos for Linq-Powered Apps
Use this when your Linq-powered app needs existing Onairos users to connect and consent from the same message thread.
Your backend loop is simple:
- Receive the Linq message webhook.
- Send the user's latest text to Onairos.
- Relay the Onairos reply back to the same Linq chat.
- After the user replies
YES, exchange the grant IDs for data.
User connection flow
Your Assistant
Linq message thread
Existing Onairos user consent inside a Linq-powered message thread.
User flow
Connect inside the message thread
The user replies in the message thread. Your backend relays each message and exchanges the grant after consent.
Text Connect Onairos
The user stays in the same Linq thread.
Verify account
Email first, then the 6-digit code.
Approve access
Connected accounts are shown before YES or NO.
Grant ready
Your backend exchanges it for the Onairos handoff.
Flow reference
Developer flow
One relay loop, five checkpoints
Keep the implementation simple: every user reply goes to Onairos, and every Onairos reply goes back to the same Linq chat.
Core loop
- 01
Inbound message
BackendStore the chat ID and sender IDs from the Linq webhook.
UserSends
Connect Onairosin the message thread. - 02
Relay loop
BackendPOST the text to Onairos, then send the returned reply through Linq.
UserSees the next Onairos prompt in the same thread.
- 03
Email verification
BackendForward the email and code replies. Onairos verifies them.
UserEnters their email, then the 6-digit code.
- 04
Explicit consent
BackendWait for YES or NO before treating anything as authorized.
UserReviews connected accounts and replies
YESorNO. - 05
Grant exchange
BackendExchange
grantIdforapiUrl,token, andauthorizedData.UserYour app can use the approved Onairos data.
The consent moment
Whichever path you integrate, consent is explicit and belongs to the user:
Consent checkpoint
Nothing is authorized until the user says YES
The existing Onairos account is verified first, then Onairos asks the user to approve the app and data categories.
Allow this app to use Gmail, LinkedIn, Reddit, and YouTube, and share personality traits and preferences?
Consent state
approved by user
Grant IDs
stored server-side
Exchange result
apiUrl, token, authorizedData
Credential rule
never expose keys or grants client-side
Copy the skill
The fastest way to integrate, and the one that works on any backend. Copy the file below into the agent that runs your thread, or hand it to your coding assistant — it has the two endpoints, the request and response shapes, and the rules. No SDK and no MCP server required.
---
name: onairos-linq-connect
description: Connect existing Onairos users from a Linq message thread and fetch their personality and preferences, from any backend.
---
# Onairos + Linq — Connect Onairos skill
Let people connect their Onairos account and share their persona from inside a Linq
message thread. Your backend relays each message between Linq and Onairos. Onairos runs
the email, verification, and consent conversation and returns the authorized data.
Works with any backend. You need two API keys, both kept server-side only:
- Onairos developer API key
- Linq Partner API key
## The loop
On every inbound Linq message:
1. Forward the message to Onairos (Endpoint 1).
2. Send the "reply" it returns back into the same Linq chat.
3. When the response contains "grants" (the user replied YES), exchange them for data (Endpoint 2).
Never send the email, verification code, or YES/NO yourself. Those must come from the
user's own messages — that is what keeps consent with the user.
## Endpoint 1 — relay a message
Forward the Linq webhook event, adding a metadata object:
POST https://api2.onairos.uk/integrations/linq/text/command
Authorization: Bearer YOUR_ONAIROS_API_KEY
Content-Type: application/json
{
...the Linq webhook event...,
"metadata": {
"applicationName": "Your App",
"agentName": "Your Assistant",
"confirmations": ["personality", "preferences"]
}
}
Response — send "reply" back into the Linq chat:
{
"success": true,
"action": "connect_account",
"reply": "What email is on your Onairos account?"
}
After the user verifies and replies YES, the response also carries grants:
{
"success": true,
"action": "authorize",
"reply": "Authorized Your App to use your connected Onairos accounts.",
"grants": [ { "grantId": "lta_abc123", "status": "active" } ]
}
## Send a reply into the Linq thread
Use your Linq Partner key and the original chat ID so the user stays in one thread:
POST https://api.linqapp.com/api/partner/v3/chats/CHAT_ID/messages
Authorization: Bearer YOUR_LINQ_PARTNER_API_KEY
Content-Type: application/json
{ "message": { "parts": [ { "type": "text", "value": "REPLY_TEXT" } ] } }
## Endpoint 2 — exchange grants for data
Only after the user replies YES. Server-to-server:
POST https://api2.onairos.uk/integrations/linq/text/grants/exchange
Authorization: Bearer YOUR_ONAIROS_API_KEY
Content-Type: application/json
{
"grantIds": ["lta_abc123"],
"confirmations": ["personality", "preferences"],
"Info": { "appName": "Your App", "confirmations": ["personality", "preferences"], "fastTraits": true }
}
Response — a handoff you use to fetch the persona:
{
"apiUrl": "https://api2.onairos.uk/traits-only-fast",
"token": "one_hour_token",
"authorizedData": { "personality": true, "preferences": true }
}
Fetch the persona by calling apiUrl with the token:
POST {apiUrl}
Authorization: Bearer {token}
Use the returned personality and preferences to personalize your replies.
## Rules
- Consent only counts when it arrives in the user's own YES message.
- Keep both API keys on the backend. Never put them in client code or message text.
- Reuse the same Linq chat ID for the whole conversation.
- Wait for "grants" in Endpoint 1's response before calling Endpoint 2.
Most teams need nothing more than this. If you would rather use a maintained package, pick a path below.
Choose your integration path
The skill above is all most teams need. For a maintained package or the full endpoint reference, both paths below run the identical flow — pick the one that fits your backend.
- With the SDK (Node). The
onairos-text-authpackage wraps the whole flow behind a single webhook call. Addonairos-text-auth-mcpon top when an LLM agent runs the thread. - Without the SDK (any backend). Call the same two Onairos endpoints directly over HTTP — from Python, Go, PHP, Ruby, or anything else — and send the Linq replies yourself.
Node backend SDK
onairos-text-auth
The Node backend SDK — one install, one webhook route. onairos-text-auth handles signature verification, event dedup, message relaying, consent replies, and grant exchange for you. All you supply is your Onairos developer API key and your Linq webhook signing secret.
npm install onairos-text-auth
import express from 'express';
import { createTextAuthRelay } from 'onairos-text-auth';
const app = express();
// Linq signs webhooks over the exact raw bytes - capture them.
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
const relay = createTextAuthRelay({
onairosApiKey: process.env.ONAIROS_API_KEY,
webhookSecret: process.env.LINQ_WEBHOOK_SECRET,
appName: 'Example App',
requestedConfirmations: ['personality', 'preferences'],
autoExchangeGrants: true
});
// Your Linq webhook route: whichever path your Linq webhook subscription
// is registered to. Linq POSTs every inbound user message here — this one
// route is where the entire text conversation arrives.
app.post('/linq/webhook', async (req, res) => {
const result = await relay.handleWebhook({
headers: req.headers,
rawBody: req.rawBody,
body: req.body,
sendMessage: ({ chatId, text }) => sendLinqReply({ chatId, text })
});
if (result.handoff) {
const data = await relay.fetchAuthorizedData({ handoff: result.handoff });
// personality traits / preferences - exactly what the user approved
}
res.json({ ok: true });
});
That is the entire integration. When a user texts Connect Onairos, the SDK runs the full conversation; the moment they authorize, result.handoff arrives in the same shape as the browser SDK handoff.
Prefer to pitch in your own words? Start the flow yourself as soon as your logic detects an opt-in:
await relay.startConnect({ body: req.body, sendMessage });
// thread receives: "What email is on your Onairos account?"
Consent always stays with the user. Your code can only start the flow or read its status — it can never approve on the user's behalf. Every YES/NO, email, and verification code has to arrive in the user's own message text, so a consent can't be forged in code.
For LLM agents
onairos-text-auth-mcp
Reach for this when an LLM agent — not fixed code — runs the thread. onairos-text-auth-mcp keeps that same guarantee under an agent: your agent decides when, deterministic code decides how. A bridge intercepts every flow message before the model sees it — verification codes and consent replies never enter its context — and exposes six tools the agent can call.
npm install onairos-text-auth onairos-text-auth-mcp
import { createTextAuthAgentBridge, buildTextAuthMcpServer } from 'onairos-text-auth-mcp';
const bridge = createTextAuthAgentBridge({ relay, sendMessage });
// Same Linq webhook route as above — the path your Linq subscription
// delivers message events to. The bridge sees every message BEFORE your
// agent and handles active auth flows deterministically.
app.post('/linq/webhook', async (req, res) => {
const incoming = await bridge.handleIncoming({
headers: req.headers, rawBody: req.rawBody, body: req.body
});
if (!incoming.ok) return res.status(401).json({ ok: false });
if (incoming.handled) return res.json({ ok: true }); // flow message - agent never sees it
await runAgentTurn(incoming.message); // your LLM, with the MCP tools below
res.json({ ok: true });
});
onairos_start_connect- start the flow after a genuine opt-inonairos_list_accounts- which platforms the user connected (silent)onairos_flow_status- flow active? authorized? data fetchable?onairos_get_data- the personalization payload, consented scopes onlyonairos_request_authorization- ask for consent; the user still replies YES themselvesonairos_disconnect- revoke grants when the user asks to stop sharing
Mount the MCP server and connect your agent
The MCP server runs in the same process as your webhook handler so it shares the bridge's flow state. Expose it over the Model Context Protocol Streamable HTTP transport, then point your agent framework at the endpoint.
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
// Same process as the webhook above - the MCP server shares bridge state.
const mcpServer = buildTextAuthMcpServer({ bridge });
app.post('/mcp', async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
res.on('close', () => transport.close());
await mcpServer.connect(transport);
await transport.handleRequest(req, res, req.body);
});
Point your agent framework's MCP client at POST /mcp - http://localhost:3000/mcp in development, or your deployed webhook host in production. The six tools register automatically once the client connects, and no verification code or consent reply ever passes through the model.
The bridge keeps flow state in memory by default, which is all you need when the MCP server and your webhook handler share one process. If you run them separately, give both a shared store ({ get, set, delete }, for example Redis-backed) through createTextAuthAgentBridge so they see the same flows.
Without the SDK
No Node? This is the exact HTTP contract the SDK wraps, so any backend — Python, Go, PHP, Ruby — can drive the same flow. You call two Onairos endpoints from your server and handle the Linq webhook signature and replies yourself; everything else behaves just like the SDK path.
Forward a Linq message to Onairos
When your app receives a Linq message event from a webhook, use Linq's webhook event docs as the source event shape, then normalize the event into the Onairos Linq text command endpoint.
POST https://api2.onairos.uk/integrations/linq/text/command
Authorization: Bearer YOUR_ONAIROS_DEVELOPER_API_KEY
Content-Type: application/json
{
"api_version": "v3",
"webhook_version": "2026-02-03",
"event_type": "message.received",
"partner_id": "linq_partner_123",
"data": {
"chat": { "id": "chat_123" },
"sender_handle": {
"id": "handle_sender",
"handle": "+12025559876",
"service": "iMessage"
},
"parts": [
{ "type": "text", "value": "Connect Onairos" }
],
"service": "iMessage"
},
"metadata": {
"applicationName": "Example App",
"agentId": "example_assistant",
"agentName": "Example Assistant",
"confirmations": ["personality", "preferences"]
}
}
Onairos returns a user-facing reply and structured state:
{
"success": true,
"action": "connect_account",
"reply": "What email is on your Onairos account?"
}
Your backend should send reply back to the same Linq chat. If you use the signed /webhook route, Onairos also mirrors the text under linq.reply.
Send the reply back through Linq
Use Linq's send-message flow from your backend. Keep the original Linq chat ID so the user stays in the same text thread. Linq's Sending Messages guide covers message parts, chat replies, idempotency, and protocol selection.
POST https://api.linqapp.com/api/partner/v3/chats/{chatId}/messages
Authorization: Bearer YOUR_LINQ_PARTNER_API_KEY
Content-Type: application/json
{
"message": {
"parts": [
{
"type": "text",
"value": "What email is on your Onairos account?"
}
]
}
}
Consent for existing users
After the user verifies their email code, Onairos checks the user's connected Onairos accounts and returns an explicit consent prompt for your app:
Your Onairos account is linked. Connected accounts found: Gmail, LinkedIn, Reddit, YouTube. Allow Example App (Example Assistant) to use these connected accounts and share personality traits and preferences? Reply YES to authorize or NO to cancel.
Only after the user replies YES does Onairos create grants. A successful response includes grant IDs:
{
"success": true,
"action": "authorize",
"reply": "Authorized Example App to use your connected Onairos accounts: Gmail, LinkedIn, Reddit, YouTube.",
"grants": [
{
"grantId": "lta_abc123",
"applicationName": "Example App",
"platform": "youtube",
"scopes": ["connected_account:use", "data:personality", "data:preferences"],
"status": "active"
}
]
}
Exchange grants for Onairos data
After consent, exchange the grant IDs from your backend. This is an app-backend call only.
POST https://api2.onairos.uk/integrations/linq/text/grants/exchange
Authorization: Bearer YOUR_ONAIROS_DEVELOPER_API_KEY
Content-Type: application/json
{
"grantIds": ["lta_abc123"],
"confirmations": ["personality", "preferences"],
"Info": {
"appName": "Example App",
"confirmations": ["personality", "preferences"],
"fastTraits": true
}
}
The response uses the same handoff shape as the Onairos SDK:
{
"apiUrl": "https://api2.onairos.uk/traits-only-fast",
"token": "one_hour_onairos_jwt",
"authorizedData": {
"basic": true,
"personality": true,
"preferences": true,
"rawMemories": false,
"fastTraits": true
},
"grant_type": "linq_text_grant",
"linq": {
"grantIds": ["lta_abc123"],
"platforms": ["youtube"],
"requestedConfirmations": ["personality", "preferences"]
}
}
Credentials
Your backend owns both credentials:
- Linq Partner API key - used by your backend to receive/send Linq messages.
- Onairos developer API key - used by your backend to call Onairos and identify your registered app.
Never put either key in browser code, message text, or client-side SDK state.
Implementation checklist
- Create the Linq webhook subscription and verify inbound requests using Linq's webhook setup and signature guidance.
- Use stable Linq identifiers on every message:
partner_id,data.chat.id,sender_handle.id, andsender_handle.handle. - Call Onairos from your backend with your Onairos developer API key.
- Relay every Onairos
replyback into the same Linq chat. - Wait for explicit
YESbefore treating anything as authorized. - Store grant IDs server-side and exchange them server-to-server.
- Do not expose Linq partner credentials, Onairos developer API keys, OAuth provider tokens, or grant IDs in client logs.
Minimal backend relay
const ONAIROS_BASE_URL = 'https://api2.onairos.uk';
async function handleLinqMessage(linqEvent) {
const onairosResponse = await fetch(
`${ONAIROS_BASE_URL}/integrations/linq/text/command`,
{
method: 'POST',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${process.env.ONAIROS_API_KEY}`
},
body: JSON.stringify({
...linqEvent,
metadata: {
applicationName: 'Example App',
agentId: 'example_assistant',
agentName: 'Example Assistant',
confirmations: ['personality', 'preferences']
}
})
}
).then((res) => res.json());
await sendLinqReply({
chatId: linqEvent.data.chat.id,
text: onairosResponse.linq?.reply || onairosResponse.reply
});
return onairosResponse;
}