Building an App
An App connects ORQO to an external platform through an adapter — a thin HTTP shell that handles channel logic only: verifying inbound webhook signatures, parsing webhook payloads into ORQO's normalized message shape, delivering outbound messages, and platform-specific formatting. Agent-callable platform operations (search, create, send) live in Python tools on the App's Skill, not in the adapter. OAuth lifecycle (initial code exchange, refresh) is owned by ORQO — your adapter never sees an OAuth code.
If your integration only needs to call a vendor API in response to agent decisions (no webhooks, no message delivery), you don't need an App at all — build a Skill with Python tools and skip this guide.
Before you start building, decide where your adapter will run: ORQO can host it for you (via Integration Builder), you can run it on your machine via the ORQO Bridge, or you can deploy it to your own infrastructure. The HTTP contract is the same in all cases. See Hosting & Runtime for the full decision matrix.
Architecture
The adapter handles webhook receipt, inbound parsing, outbound delivery, and platform formatting. Agent-callable operations run as Python tools inside ORQO's engine and call the external API directly — they don't go through your adapter.
Build a Channel-Bearing Integration
Your adapter is a channel-bearing integration if any of these are true:
- The platform sends you webhooks you need to receive (Slack events, GitHub pushes, inbound email).
- The platform requires platform-specific message delivery (posting a Slack message, sending a Telegram update).
- The platform feeds a document source (Drive, Gmail, GitHub repo files).
If none of those apply, you don't need an adapter — build a Skill with Python tools instead. Bannerbear, OpenAI, and similar pure-API integrations are Skills, not Apps.
Step 1: Build the Adapter
An adapter is a stateless HTTP service that implements the Adapter Contract. Use any language or framework — the HTTP contract is what matters.
Required Endpoints
Every adapter implements:
| Endpoint | Purpose |
|---|---|
GET /manifest.json | Serve the adapter manifest (see Step 2) |
GET /health | Health check. Return { "status": "ready", "version": "<sha>", "adapter": "<slug>" } — see Adapter Contract Reference |
POST /verify-webhook | Verify inbound webhook signatures |
POST /parse-inbound | Normalize inbound webhooks into ORQO's format |
POST /deliver | Send a message to the external platform |
POST /format | Format messages for the platform's display conventions |
Optional, for adapters that back ORQO document sources (Drive, Gmail, GitHub-shaped):
| Endpoint | Purpose |
|---|---|
POST /sources/list-changes | Return files added/modified/removed since a cursor |
POST /sources/fetch-file | Return a single file's bytes |
See the Adapter Contract Reference for complete request/response specifications.
- No
/mcpendpoint. Agent-callable tools (gmail_search,slack_send_message, etc.) are Python tools on the App's Skill, not adapter endpoints. - No
/oauth/callback. ORQO drives the OAuth flow generically using theoauth_configblock in your manifest. Your adapter receives a fresh access token in thecredentialsbody of every request.
Where Agent-Callable Tools Live
Anything an agent calls — search, list, create, post, send — is a Python tool on the App's Skill. Tools are authored either:
- By you, in the Developer Portal, when you build the App by hand. Each tool is a Python function with a typed signature; ORQO's engine compiles and runs it at request time.
- By ORQO's IntegrationBuilder + Tool Coder agents, when you ask IntegrationBuilder to build a new platform integration end-to-end (see Step 3 (option A)).
Either way, the tools share the App's credential row. When an agent invokes slack_send_message, the engine passes the credential to the Python function — no HTTP hop through your adapter, no MCP protocol crossing.
See Building a Skill for tool authoring details.
Authentication
ORQO passes the credential record in the request body of every adapter call, under credentials. For OAuth-backed apps, the access token is refreshed on ORQO's schedule before the call, so your handler can read credentials.access_token directly. See Credential Lifecycle for how this works.
Step 2: Serve the Manifest
Your adapter must serve a manifest.json file at its root URL. This tells ORQO everything it needs to install and configure your App.
GET https://your-adapter.example.com/manifest.json
Manifest Format
{
"schema_version": "1.0",
"name": "My Service",
"description": "Connect ORQO agents to My Service for search and automation.",
"icon": "puzzle-piece",
"category": "productivity",
"version": "1.0.0",
"capabilities": [],
"credentials": [
{
"key": "MY_SERVICE_TOKEN",
"type": "api_key",
"label": "My Service API Key",
"maps_to": "token"
}
],
"skill": {
"name": "My Service",
"description": "Search and manage items in My Service.",
"knowledge": "You can search and manage items in My Service. Use my_service_search to find items by keyword. Use my_service_create to add new items."
},
"setup_instructions": [
"Create an API key in your My Service dashboard",
"Enter the API key when prompted during installation",
"Assign the skill to your agents in the Team Builder"
],
"author": {
"name": "Your Company",
"url": "https://example.com"
},
"homepage": "https://docs.example.com/orqo-integration"
}
Manifest Fields
| Field | Required | Description |
|---|---|---|
schema_version | Yes | Manifest schema version. Currently "1.0". |
name | Yes | Display name for your integration. |
description | No | Short description of what the integration does. |
icon | No | Heroicon name. Defaults to "puzzle-piece". |
category | No | One of: communication, storage, productivity, social, research, development, connectivity, content_creation, knowledge, integration, operations, data. |
version | No | Your adapter's version string. |
capabilities | No | ["send"], ["receive"], or ["send", "receive"] for channel apps. |
credentials | No | Array of credential definitions (see below). |
oauth_config | No | OAuth configuration for providers that use OAuth 2.0 (see below). ORQO drives the flow on the App's behalf. |
receive_config | No | Configuration for inbound webhook handling. |
skill | No | Auto-created Skill definition (see below). |
setup_instructions | No | Array of setup steps shown to users during installation. |
author | No | Object with name and optional url. |
homepage | No | URL to your integration's documentation. |
logo_url | No | URL to your integration's logo image. |
Credentials
Each credential defines an authentication requirement:
{
"credentials": [
{
"key": "MY_SERVICE_TOKEN",
"type": "api_key",
"label": "My Service API Key",
"maps_to": "token"
}
]
}
| Field | Required | Description |
|---|---|---|
key | Yes | Unique identifier for this credential (e.g., SLACK_BOT_TOKEN, GOOGLE_OAUTH2). Multiple Apps and Skills sharing the same key will share the same credential record. |
type | Yes | One of api_key, oauth2, or text (see table below). |
label | No | Human-readable label shown in the UI. Defaults to key. |
maps_to | No | The credential field name your adapter expects. When ORQO passes credentials to your adapter (in /deliver, /verify-webhook, /sources/*), it maps the stored credential value to this field name. For example, "maps_to": "token" means your adapter receives { "token": "..." }. |
| Credential Type | What It Stores | When to Use |
|---|---|---|
api_key | A single token/key | API keys, bot tokens, webhook secrets |
oauth2 | Client ID, client secret, access token, refresh token | Services with OAuth 2.0 flows |
text | Custom text fields | Multi-field credentials (SMTP host/port/user/pass) |
Real-World Examples
// OAuth2 — maps the stored access_token field
{ "key": "GOOGLE_OAUTH2", "type": "oauth2", "label": "Google OAuth2 Token", "maps_to": "access_token" }
// API key — maps the stored token field
{ "key": "GITHUB_TOKEN", "type": "api_key", "label": "Personal Access Token", "maps_to": "token" }
// Multiple credentials for one adapter
[
{ "key": "SLACK_BOT_TOKEN", "type": "api_key", "label": "Bot User OAuth Token", "maps_to": "token" },
{ "key": "SLACK_SIGNING_SECRET", "type": "text", "label": "Signing Secret", "maps_to": "signing_secret" }
]
OAuth Configuration
For services that use OAuth 2.0, add oauth_config:
{
"oauth_config": {
"authorize_url": "https://provider.com/oauth/authorize",
"token_url": "https://provider.com/oauth/token",
"scopes": ["read", "write"],
"scope_separator": " ",
"extra_authorize_params": {
"access_type": "offline",
"prompt": "consent"
}
}
}
When a user installs your App, ORQO presents a Connect with OAuth button that initiates the OAuth flow using this configuration. Tokens are stored securely and refreshed automatically.
Skill Definition
If your App should expose agent-callable Python tools, include a skill definition. Installing your App will auto-create the Skill; you (or IntegrationBuilder's Tool Coder) then attach Python tool definitions to it.
{
"skill": {
"name": "Google Drive",
"description": "Search, read, and manage files in Google Drive.",
"knowledge": "You can access Google Drive. Use google_drive_search to find files. Use google_drive_read to read file contents. Use google_drive_list to browse folders. Prefer searching over browsing when looking for specific documents."
}
}
The knowledge field is injected into every agent's context when this skill is assigned. Write it as concise instructions listing each tool by name.
A single App can host multiple Skills sharing one OAuth (e.g. one Google App with separate Drive, Calendar, and Gmail Skills). For multi-skill apps, declare each skill as a separate Skill in the Developer Portal under the App.
Omit skill for Apps that only handle channel communication (send/receive) without exposing tools to agents. For example, an inbound email gateway that only receives webhooks and routes them to conversations doesn't need agent-facing tools.
Step 3 (option A): Let IntegrationBuilder Build It For You
If you don't want to hand-write Sinatra and Python, ask IntegrationBuilder in-app to build the integration. The flow:
- Scaffold. IntegrationBuilder produces a thin Sinatra shell with the four channel endpoints (
/verify-webhook,/parse-inbound,/deliver,/format) defined as501 Not Implementedstubs, plus aspec/directory with a Minitest + Rack::Test harness. The shell deploys to ORQO in roughly 10 seconds via the Service Artifact loader (the adapter's source lives as a Spaces blob; ORQO's loader picks up new revisions on a/loader/reloadping). - Fill the channel handlers. IntegrationBuilder calls its
write_adapter_handlertool once per endpoint. Each call writes both the Sinatra handler and a Minitest spec for it (sample fixture extracted from the platform's docs, asserts the response shape). The new revision redeploys;validate_appruns the spec suite and reports per-endpoint pass/fail with assertion details. IntegrationBuilder iterates until green. - Author the Python tools. IntegrationBuilder delegates to Tool Coder, which writes one Python
ToolDefinitionper agent-callable operation (search, create, send, etc.). Tools are bundled into the App's Skill. - Validate. A final end-to-end check confirms manifest, health, every channel endpoint, the OAuth flow, and every tool work together.
The output is a complete, working integration: thin Sinatra adapter (channel handlers only) + Skill with Python tools, deployed and ready to install. There are no TODO stubs and no Ruby tool code — agent-callable operations are Python, channel handlers are Ruby, OAuth lives in ORQO.
If you prefer to write the adapter yourself, continue with Step 3 (option B).
Step 3 (option B): Register in the Developer Portal
- Sign in to the Developer Portal with your Google or GitHub account
- Navigate to the Apps section (
/developer/apps) - Click + New App
- Enter your adapter's base URL (e.g.,
https://your-adapter.example.com) - ORQO fetches your
manifest.jsonand shows a preview - Review the parsed information — name, description, capabilities, credentials
- Confirm to create a draft listing
- The detail page opens at
/developer/apps/:slug, showing all parsed manifest sections - Click Verify to probe your adapter's
/healthendpoint and exercise the channel handlers — confirm reachability before publishing - Author the Python tools that back the App's Skill (or skip if your App is channel-only)
- Edit your listing details (tagline, logo, pricing) from the Edit page
- Click Submit for Review when ready
After review, your integration is published to the marketplace where organizations can install it.
The Detail Page
Every listing has a detail page that displays:
- Overview — name, description, adapter URL, capabilities, homepage
- Required Credentials — the credential table from your manifest
- Skills — each Skill backed by this App, with its knowledge and Python tool definitions
- OAuth Configuration — authorize URL, token URL, and scopes
- Setup Instructions — the step-by-step list from your manifest
- Raw Manifest — collapsible section with the full JSON
Verifying Your Adapter
The Verify button on the detail page checks GET /health returns HTTP 2xx and that the manifest is current. Use this to confirm your adapter is reachable before submitting for review.
Keeping Your Manifest Up to Date
When you deploy changes to your adapter (new capabilities, updated descriptions), click Refresh on your listing in the Developer Portal. This re-fetches the manifest and updates the listing.
Hosting & Deployment
If you self-host the adapter, deploy it however you'd like — ORQO only needs the public URL. If you let ORQO host it (the default for IntegrationBuilder-built integrations), the adapter source lives as a Service Artifact blob in object storage; ORQO's loader picks up new revisions in roughly 10 seconds when you publish a change.
Checklist
Before submitting your App for review:
-
manifest.jsonis served at{adapter_url}/manifest.jsonand hasschema_versionandname - Adapter has
/healthreturning{ status: "ready", version, adapter } - Channel handlers
/verify-webhook,/parse-inbound,/deliver,/formatare implemented (not 501 stubs) - If using OAuth,
oauth_confighas correctauthorize_url,token_url, andscopes— your adapter does not implement/oauth/callback -
setup_instructionsare specific and actionable - If the App has a Skill with Python tools, all tool names are prefixed with the service name (e.g.,
gmail_search, notsearch) - If the App has a Skill,
skill.knowledgelists every tool by name with usage guidance - No product-specific names in knowledge text (use generic examples)
- Channel apps implement feedback loop prevention
- If supporting document sources,
/sources/list-changesand/sources/fetch-fileare implemented