Skip to main content

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:

EndpointPurpose
GET /manifest.jsonServe the adapter manifest (see Step 2)
GET /healthHealth check. Return { "status": "ready", "version": "<sha>", "adapter": "<slug>" } — see Adapter Contract Reference
POST /verify-webhookVerify inbound webhook signatures
POST /parse-inboundNormalize inbound webhooks into ORQO's format
POST /deliverSend a message to the external platform
POST /formatFormat messages for the platform's display conventions

Optional, for adapters that back ORQO document sources (Drive, Gmail, GitHub-shaped):

EndpointPurpose
POST /sources/list-changesReturn files added/modified/removed since a cursor
POST /sources/fetch-fileReturn a single file's bytes

See the Adapter Contract Reference for complete request/response specifications.

What's not on the adapter
  • No /mcp endpoint. 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 the oauth_config block in your manifest. Your adapter receives a fresh access token in the credentials body 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

FieldRequiredDescription
schema_versionYesManifest schema version. Currently "1.0".
nameYesDisplay name for your integration.
descriptionNoShort description of what the integration does.
iconNoHeroicon name. Defaults to "puzzle-piece".
categoryNoOne of: communication, storage, productivity, social, research, development, connectivity, content_creation, knowledge, integration, operations, data.
versionNoYour adapter's version string.
capabilitiesNo["send"], ["receive"], or ["send", "receive"] for channel apps.
credentialsNoArray of credential definitions (see below).
oauth_configNoOAuth configuration for providers that use OAuth 2.0 (see below). ORQO drives the flow on the App's behalf.
receive_configNoConfiguration for inbound webhook handling.
skillNoAuto-created Skill definition (see below).
setup_instructionsNoArray of setup steps shown to users during installation.
authorNoObject with name and optional url.
homepageNoURL to your integration's documentation.
logo_urlNoURL 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"
}
]
}
FieldRequiredDescription
keyYesUnique 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.
typeYesOne of api_key, oauth2, or text (see table below).
labelNoHuman-readable label shown in the UI. Defaults to key.
maps_toNoThe 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 TypeWhat It StoresWhen to Use
api_keyA single token/keyAPI keys, bot tokens, webhook secrets
oauth2Client ID, client secret, access token, refresh tokenServices with OAuth 2.0 flows
textCustom text fieldsMulti-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.

When to omit skill

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:

  1. Scaffold. IntegrationBuilder produces a thin Sinatra shell with the four channel endpoints (/verify-webhook, /parse-inbound, /deliver, /format) defined as 501 Not Implemented stubs, plus a spec/ 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/reload ping).
  2. Fill the channel handlers. IntegrationBuilder calls its write_adapter_handler tool 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_app runs the spec suite and reports per-endpoint pass/fail with assertion details. IntegrationBuilder iterates until green.
  3. Author the Python tools. IntegrationBuilder delegates to Tool Coder, which writes one Python ToolDefinition per agent-callable operation (search, create, send, etc.). Tools are bundled into the App's Skill.
  4. 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

  1. Sign in to the Developer Portal with your Google or GitHub account
  2. Navigate to the Apps section (/developer/apps)
  3. Click + New App
  4. Enter your adapter's base URL (e.g., https://your-adapter.example.com)
  5. ORQO fetches your manifest.json and shows a preview
  6. Review the parsed information — name, description, capabilities, credentials
  7. Confirm to create a draft listing
  8. The detail page opens at /developer/apps/:slug, showing all parsed manifest sections
  9. Click Verify to probe your adapter's /health endpoint and exercise the channel handlers — confirm reachability before publishing
  10. Author the Python tools that back the App's Skill (or skip if your App is channel-only)
  11. Edit your listing details (tagline, logo, pricing) from the Edit page
  12. 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.json is served at {adapter_url}/manifest.json and has schema_version and name
  • Adapter has /health returning { status: "ready", version, adapter }
  • Channel handlers /verify-webhook, /parse-inbound, /deliver, /format are implemented (not 501 stubs)
  • If using OAuth, oauth_config has correct authorize_url, token_url, and scopes — your adapter does not implement /oauth/callback
  • setup_instructions are 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, not search)
  • If the App has a Skill, skill.knowledge lists 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-changes and /sources/fetch-file are implemented