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.mdto define the expected payload schema, includingbase_url,path, andparams. - Delegate HTTP logic to the generic
rest_request.pyclient, importing itsexecute()function to handle GET/POST, JSON normalization, and pagination truncation. - Register the skill in
.codex-plugin/plugin.jsonso Codex can discover and invoke it. - Leverage built-in limits such as
max_itemsandmax_depthto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →