Custom HTTP integrations
Turn any HTTP API into catalog tools with a JSON manifest.
A custom HTTP integration registers your own HTTP API as provider tools. Each operation you declare becomes a callable tool (custom_http.<slug>.<operation>) usable from tool columns, workflows, and recipes — with auth handled by Oxygen, not your code.
Use it when the recipe-native HTTP tools are not enough: oxygen.http_json_request (GET) and oxygen.http_json_post (POST with JSON body) only reach anonymous public HTTPS endpoints — no credentials, no custom headers. Custom HTTP operations support GET, POST, PUT, PATCH, and DELETE with typed body/query/path fields and stored credentials.
Registering
oxygen custom-integrations apply --manifest ./my-integration.json --json
oxygen custom-integrations list --jsonapply requires exactly one of --manifest <path> (a JSON file) or --manifest-json '<json>' (inline). Credentials are not passed here — see Credentials.
Manifest shape
{
"version": 1,
"slug": "lead_scorer",
"name": "Lead Scorer",
"description": "Score leads via our internal API.",
"baseUrl": "https://api.example.com/",
"auth": { "type": "bearer" },
"operations": [
{
"operation": "score_lead",
"displayName": "Score Lead",
"method": "POST",
"path": "/score",
"summary": "Score a lead by email.",
"effect": "read",
"fields": [
{ "key": "email", "type": "string", "required": true, "description": "Lead email.", "location": "body" }
]
}
]
}This mints the tool custom_http.lead_scorer.score_lead.
Top-level fields
| Field | Required | Notes |
|---|---|---|
version | No | Defaults to 1; must be 1 if set. |
slug | Yes | Integration id. Must match ^[a-z][a-z0-9_]{0,62}$. |
name | Yes | Display name. |
description | No | Defaults to a generated description. |
baseUrl | Yes | Public HTTPS URL. Private/internal hosts are rejected. |
auth | No | Defaults to { "type": "none" }. See Auth. |
operations | Yes | Non-empty array. Operation ids must be unique. |
Operation fields
| Field | Required | Notes |
|---|---|---|
operation | Yes | Operation id, same slug rules as above. |
method | Yes | GET, POST, PUT, PATCH, or DELETE. |
path | Yes | Must start with /. Appended to baseUrl; supports {param} segments filled by location: "path" fields. |
displayName | No | Defaults to a titleized operation id. |
summary | No | Defaults to <METHOD> <path>. |
effect | No | read (default) or write. Writes follow approval rules. |
sideEffectClass | No | Derived from effect unless set. |
fields | No | Typed inputs — see below. With no fields, a GET forwards the raw input as query params and other methods forward it as the JSON body. |
timeoutMs | No | Default 10000, max 30000. |
maxResponseBytes | No | Default 1000000, max 5000000. |
outputPath | No | Dot-path to project out of the response. |
inputSchema / outputSchema | No | Optional JSON Schemas for the tool descriptor. |
Field entries
| Field | Required | Notes |
|---|---|---|
key | Yes | Input name (slug rules). |
type | No | string (default), number, boolean, string[], number[], object, object[], array. |
required | No | Defaults to true. |
description | No | Defaults to the key. |
location | No | body, query, or path. Defaults to query on GET, otherwise body. |
wireKey | No | Rename the key on the wire. |
Auth
auth.type | Extra fields | Sent as |
|---|---|---|
none | — | — |
bearer | — | Authorization: Bearer <secret> |
api_key_header | header, prefix? | <header>: <prefix><secret> |
api_key_query | param | ?<param>=<secret> |
basic | username | Authorization: Basic base64(<username>:<secret>) |
Credentials
The manifest never contains the secret (apply rejects a secrets field). After apply, attach the token/key from the terminal:
oxygen custom-integrations connect lead_scorer --secrets-file ./.env--secrets-file reads a .env-style file (a single entry, or one named API_KEY/TOKEN/PASSWORD/SECRET) so the credential stays out of shell history; --api-key <value> passes it inline. The web Connections page (/connections/custom_http) does the same thing. Either way, Oxygen stores the credential encrypted and injects it server-side at call time — recipe and column code never sees it, and it never enters a recipe bundle or run log.
Calling the tools
oxygen tools get custom_http.lead_scorer.score_lead --json
oxygen tools run custom_http.lead_scorer.score_lead --input-json '{"email":"a@b.com"}' --mode dry-run --jsonIn a recipe, allowlist the tool id and call it with ctx.tools.run("custom_http.lead_scorer.score_lead", { email }, { key }). In a table, bind it as a tool column.
Related
- Integrations — connection state and auth modes.
- Recipes — calling custom tools from workflow code.
- Approvals — write-effect operations and live runs.