OXYGENOxygen/ Docs
Providers

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 the credential injected server-side.

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 --json

apply 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

FieldRequiredNotes
versionNoDefaults to 1; must be 1 if set.
slugYesIntegration id. Must match ^[a-z][a-z0-9_]{0,62}$.
nameYesDisplay name.
descriptionNoDefaults to a generated description.
baseUrlYesPublic HTTPS URL. Private/internal hosts are rejected. A base path and query are preserved when the operation path is appended.
authNoDefaults to { "type": "none" }. See Auth.
operationsYesNon-empty array. Operation ids must be unique.

Operation fields

FieldRequiredNotes
operationYesOperation id, same slug rules as above.
methodYesGET, POST, PUT, PATCH, or DELETE.
pathYesMust start with /. Appended to baseUrl; supports {param} segments filled by location: "path" fields.
displayNameNoDefaults to a titleized operation id.
summaryNoDefaults to <METHOD> <path>.
effectNoread (default) or write. Writes follow approval rules.
sideEffectClassNoDerived from effect unless set.
idempotentNoDefaults to true for GET and false otherwise. Set true only when replaying the same request is safe; this lets a reclaimed worker retry after an ambiguous interruption.
fieldsNoTyped inputs — see below. With no fields, a GET forwards the raw input as query params and other methods forward it as the JSON body.
timeoutMsNoDefault 10000, max 300000 (5 minutes). Values outside the supported range are rejected rather than silently clamped.
maxResponseBytesNoDefault 1000000, max 5000000.
outputPathNoDot-path to project out of the response.
inputSchema / outputSchemaNoOptional JSON Schemas for the tool descriptor.

Field entries

FieldRequiredNotes
keyYesInput name (slug rules).
typeNostring (default), number, boolean, string[], number[], object, object[], array. integer is accepted as an alias for number.
requiredNoDefaults to true.
descriptionNoDefaults to the key.
locationNobody, query, or path. Defaults to query on GET, otherwise body.
wireKeyNoRename the key on the wire.

Auth

auth.typeExtra fieldsSent as
none——
bearer—Authorization: Bearer <secret>
api_key_headerheader, prefix?<header>: <prefix><secret>
api_key_queryparam?<param>=<secret>
basicusernameAuthorization: Basic base64(<username>:<secret>)

Compatibility aliases are accepted for imported manifests: api_key for api_key_header, headerName for header, and slug, id, or key for an operation id. New manifests should use the canonical names above.

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. You can also attach the credential from the web Connections page (/connections/custom_http). 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":"[email protected]"}' --mode dry-run --json

Custom HTTP tools use the no_bill credit posture: Oxygen charges 0 credits and the connected organization owns any upstream API cost. A live write operation still requires explicit approval, but no max_credits value; read-only operations do not require approval. Run summaries echo idempotent, timeout_ms, max_response_bytes, and output_path so the execution contract remains inspectable.

In 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.

Custom integration vs. inline HTTP column

Two different things share the "Custom HTTP" name. Pick by whether the request is reusable:

A. Reusable custom integration (this page). oxygen custom-integrations apply registers org-wide custom_http.<slug>.<operation> tools; the credential is stored encrypted in Oxygen, and the tools run in the background from tool columns, workflows, and recipes with billing and provenance.

B. Inline HTTP column (the web app's "Add column → HTTP API"). A single tool column whose definition uses mode: "custom_http" with an inline request block. It runs in the background on Oxygen like any other tool column — web, CLI, and MCP all queue the same durable run — and {{column_key}} tokens in the url, headers, and body are substituted from each row.

Where an inline column's secrets come from

A secrets entry is a reference, never a value: {"acme": {"env": "ACME_KEY"}}, used in the request as {{secrets.acme}}. Where ACME_KEY is read depends on where the column runs, and the two are deliberately different:

Run{"env": "ACME_KEY"} resolves from
Background (web, CLI, MCP)this workspace's registered custom integration with slug acme_key, reading its encrypted api_key secret
oxygen columns run <table> <column> --localthe environment variable ACME_KEY on your own machine

Oxygen never reads the server's own environment for a workspace column, so a background run whose reference names no registered integration fails that row with custom_http_secret_unavailable rather than silently sending a blank credential. Register it once and every surface can run the column:

oxygen custom-integrations apply --slug acme_key --name "Acme" --base-url https://api.acme.com --auth bearer
oxygen custom-integrations connect acme_key --api-key "$ACME_KEY"

Use --local when the key must not leave your machine.

Effect, approval, and cost for an inline column

A GET is a read: it needs no --approved and no --max-credits. Any other method is an external write and requires --approved like every other side-effecting tool column. Either way Oxygen charges 0 credits — you own the upstream API cost — and --dry-run previews the templated request for one real row with every secret masked as ***, naming which references this workspace can resolve and which it cannot.

oxygen columns run leads lead_score --limit 1 --dry-run --json
oxygen columns run leads lead_score --limit 25 --background --json
  • Integrations — connection state and auth modes.
  • Recipes — calling custom tools from workflow code.
  • Approvals — write-effect operations and live runs.

On this page