How to Integrate Third-Party APIs into Codex Plugins: A Complete Guide

You integrate third-party APIs into Codex plugins by defining skill metadata in SKILL.md, reusing the generic rest_request.py client for HTTP requests, and registering the skill in .codex-plugin/plugin.json to enable discovery.

Codex plugins extend OpenAI's capabilities by exposing sandboxed skills that can call external services. According to the openai/plugins repository, the recommended pattern to integrate third-party APIs into Codex plugins relies on a minimal, reusable HTTP client that keeps your codebase dependency-light and audit-friendly.

Understanding the Codex Plugin Architecture

Codex plugins run inside a sandboxed Python interpreter. Each skill lives in a dedicated directory under skills/, containing a SKILL.md file describing inputs and a scripts/ folder containing the execution logic. The generic REST client handles all network traffic, normalizing responses and enforcing safety limits. This architecture isolates third-party calls to a single well-tested module, making it easy to unit-test and secure.

The file structure follows this convention:


└─ plugin-root/
   ├─ .codex-plugin/
   │   └─ plugin.json            ← discovery manifest
   └─ skills/
       └─ <skill-name>/
           ├─ SKILL.md           ← interface schema
           └─ scripts/
               └─ rest_request.py← reusable HTTP client

Step-by-Step Implementation

Step 1: Define Skill Metadata in SKILL.md

Create a SKILL.md file that declares the skill's interface. This file tells Codex what inputs to expect, including the base_url, path, and any params or headers required by the third-party API.

The following example from plugins/life-science-research/skills/uniprot-skill/SKILL.md demonstrates the schema for a UniProt query skill:

name: uniprot-skill
description: Query UniProt for protein information.
input_schema:
  type: object
  properties:
    base_url:
      const: https://rest.uniprot.org
    path:
      type: string
      enum:
        - uniprotkb/search
        - uniprotkb/{accession}
    params:
      type: object
      additionalProperties: true
  required:
    - base_url
    - path

Step 2: Reuse the Generic REST Client

Instead of importing heavy HTTP libraries, import the execute() function from rest_request.py. This client handles GET/POST requests, JSON parsing, pagination truncation via max_items and max_depth, and optional raw response saving via save_raw.

The following run_uniprot.py script, located at plugins/life-science-research/skills/uniprot-skill/scripts/run_uniprot.py, demonstrates the minimal wrapper pattern:

#!/usr/bin/env python3
import json
import sys
from pathlib import Path

# Import the generic client used across dozens of skills

sys.path.append(str(Path(__file__).parent / "scripts"))
from rest_request import execute, error

def main() -> int:
    # Load the payload from stdin

    try:
        payload = json.load(sys.stdin)
    except Exception as exc:
        sys.stdout.write(json.dumps(error("invalid_json", f"Could not parse JSON input: {exc}")))
        return 2

    # Delegate to the shared client

    output = execute(payload)

    # Return normalized JSON

    sys.stdout.write(json.dumps(output))
    return 0 if output.get("ok") else 1

if __name__ == "__main__":
    raise SystemExit(main())

The execute() function in rest_request.py builds the full URL, merges headers, executes the request with a short timeout, and returns a normalized document containing the HTTP status, compacted records, and truncation warnings.

Step 3: Register the Skill in plugin.json

Add the skill to .codex-plugin/plugin.json so Codex can discover it. The manifest lists the skill name and path relative to the plugin root, as seen in plugins/daloopa/.codex-plugin/plugin.json:

{
  "name": "uniprot",
  "version": "0.1.0",
  "skills": [
    {
      "name": "uniprot-skill",
      "path": "skills/uniprot-skill"
    }
  ]
}

Complete Working Example: UniProt Integration

When the skill is invoked, Codex passes a JSON payload matching the SKILL.md schema. The following bash command demonstrates a live request to the UniProt API using the skill wrapper:

cat <<'EOF' | python plugins/life-science-research/skills/uniprot-skill/scripts/run_uniprot.py
{
  "base_url": "https://rest.uniprot.org",
  "path": "uniprotkb/search",
  "method": "GET",
  "params": {
    "query": "gene:TP53 AND organism_id:9606",
    "fields": "accession,gene_names",
    "size": 5,
    "format": "json"
  },
  "response_format": "json",
  "max_items": 5,
  "max_depth": 2
}
EOF

The response is a normalized JSON object that includes metadata about the request and the truncated record set:

{
  "ok": true,
  "source": "rest-uniprot-org",
  "path": "uniprotkb/search",
  "method": "GET",
  "status_code": 200,
  "record_path": "results",
  "record_count_returned": 5,
  "record_count_available": 5,
  "truncated": false,
  "records": [
    { "accession": "P04637", "gene_names": "TP53" }
  ],
  "warnings": []
}

This pattern works for any RESTful service: simply change the base_url, path, and params while reusing the same rest_request.py infrastructure.

Security and Safety Considerations

The rest_request.py client validates input types, sanitizes strings, and enforces resource limits to prevent runaway requests. Key safety mechanisms include:

  • max_items – Caps the number of returned records to prevent oversized responses.
  • max_depth – Limits JSON nesting depth during truncation.
  • save_raw – Optional flag to store raw responses on temporary files for debugging without cluttering stdout.
  • Input validation – The execute() function rejects malformed URLs or invalid JSON payloads before network execution.

By centralizing all HTTP logic in rest_request.py, you isolate third-party network traffic to a single auditable module, reducing the attack surface and simplifying compliance testing.

Summary

  • Describe the interface in SKILL.md to define the expected payload schema, including base_url, path, and params.
  • Delegate HTTP logic to the generic rest_request.py client, importing its execute() function to handle GET/POST, JSON normalization, and pagination truncation.
  • Register the skill in .codex-plugin/plugin.json so Codex can discover and invoke it.
  • Leverage built-in limits such as max_items and max_depth to maintain sandbox safety and prevent resource exhaustion.

Frequently Asked Questions

What file declares the schema for a Codex plugin skill?

The SKILL.md file declares the schema. It lives inside the skill directory (e.g., skills/uniprot-skill/SKILL.md) and defines the input properties, required fields, and constants like base_url that Codex validates before execution.

How does the generic REST client handle large API responses?

The execute() function in rest_request.py truncates large responses using the max_items and max_depth parameters. It returns a compacted view of the data while indicating truncation status in the truncated field of the response JSON, preventing memory issues in the sandboxed environment.

Where do I register a new skill so Codex can discover it?

Register the skill in .codex-plugin/plugin.json. Add an entry to the skills array specifying the skill name and its path relative to the plugin root, as demonstrated in plugins/daloopa/.codex-plugin/plugin.json.

Can I use custom HTTP headers when integrating third-party APIs?

Yes. The rest_request.py client accepts a headers object in the input payload. These headers are merged with any default headers before the request is executed, allowing you to pass authentication tokens or content-type specifications required by third-party services.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →