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

> Integrate third-party APIs into Codex plugins by defining skill metadata, using the rest_request.py client, and registering your skill. Follow our complete guide for seamless integration.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: how-to-guide
- Published: 2026-06-29

---

**You integrate third-party APIs into Codex plugins by defining skill metadata in [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md), reusing the generic [`rest_request.py`](https://github.com/openai/plugins/blob/main/rest_request.py) client for HTTP requests, and registering the skill in [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/life-science-research/skills/uniprot-skill/SKILL.md) demonstrates the schema for a UniProt query skill:

```markdown
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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/run_uniprot.py) script, located at [`plugins/life-science-research/skills/uniprot-skill/scripts/run_uniprot.py`](https://github.com/openai/plugins/blob/main/plugins/life-science-research/skills/uniprot-skill/scripts/run_uniprot.py), demonstrates the minimal wrapper pattern:

```python
#!/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.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`](https://github.com/openai/plugins/blob/main/plugins/daloopa/.codex-plugin/plugin.json):

```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`](https://github.com/openai/plugins/blob/main/SKILL.md) schema. The following bash command demonstrates a live request to the UniProt API using the skill wrapper:

```bash
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:

```json
{
  "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`](https://github.com/openai/plugins/blob/main/rest_request.py) infrastructure.

## Security and Safety Considerations

The [`rest_request.py`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/SKILL.md) to define the expected payload schema, including `base_url`, `path`, and `params`.
- **Delegate HTTP logic** to the generic [`rest_request.py`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.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`](https://github.com/openai/plugins/blob/main/SKILL.md) file declares the schema. It lives inside the skill directory (e.g., [`skills/uniprot-skill/SKILL.md`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/.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`](https://github.com/openai/plugins/blob/main/plugins/daloopa/.codex-plugin/plugin.json).

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

Yes. The [`rest_request.py`](https://github.com/openai/plugins/blob/main/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.