# How to Integrate External Services with an OpenAI Plugin: A Complete Guide

> Learn to integrate external services with your OpenAI plugin. Our guide covers manifest creation, secure HTTP request handling, and deterministic command chaining for your openai plugins.

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

---

**To integrate external services with an OpenAI plugin, you create a declarative manifest, implement explicit-only skills that handle HTTP requests securely via environment variables, and optionally chain them into deterministic commands.**

The `openai/plugins` repository (also known as the Codex plugins monorepo) provides a standardized framework for exposing REST APIs, SDKs, and OAuth-protected resources to Large Language Models (LLMs). This architecture ensures that every external call is predictable, auditable, and secure by design.

## Architecture of External Service Integration

OpenAI plugins use a layered architecture that separates user interaction from external API execution. When you integrate external services with an OpenAI plugin, the request flows through explicit skill invocations rather than implicit tool calls.

The architectural flow follows this pattern:

1. **User Prompt** triggers a `/command` or direct `$skill` reference
2. **Command layer** (optional) orchestrates multi-step workflows
3. **Skill layer** performs the actual external service call via Python or Node.js implementations
4. **Implementation scripts** load credentials from `.env` files, build HTTP requests, and return JSON payloads
5. **LLM** receives the structured result to continue the conversation

In [`plugins/zoom/skills/rest-api/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/SKILL.md), this pattern is demonstrated through a REST API skill that explicitly declares its capabilities and refuses implicit invocation via `policy.allow_implicit_invocation: false`.

## Core Components for Service Integration

### Plugin Manifest ([`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json))

Every plugin requires a machine-readable manifest located at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json). This file describes the plugin's capabilities, entry points, and metadata. The Zoom plugin manifest at [`plugins/zoom/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/zoom/.codex-plugin/plugin.json) serves as the canonical reference, defining skills, commands, and assets that Codex can discover.

### Explicit-Only Skills ([`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md))

Skills are the primary mechanism to integrate external services with an OpenAI plugin. Each skill contains:

- **Declarative metadata** in [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) specifying input/output schemas and invocation policies
- **Implementation code** in `scripts/` directories (commonly Python) that execute HTTP requests
- **Security boundaries** ensuring credentials never reach the LLM context window

The [`plugins/zoom/skills/rest-api/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/SKILL.md) file illustrates how to document endpoint contracts while keeping the actual request logic in separate implementation files.

### Deterministic Commands (`commands/`)

Commands chain multiple skills into reusable workflows. The file [`plugins/zoom/commands/build-zoom-rest-api-app.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/commands/build-zoom-rest-api-app.md) demonstrates how to sequence authentication, API calls, and verification steps into a single deterministic path that users invoke via `/build-zoom-rest-api-app`.

### Reviewer Agents (`agents/`)

Security-focused agents audit integrations before deployment. The [`plugins/zoom/agents/zoom-integration-reviewer.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/agents/zoom-integration-reviewer.md) file shows how automated reviewers validate that external service calls follow security protocols and handle errors correctly.

## Step-by-Step Guide to Integrate External Services

Follow these eight steps to add a new external service integration to the `openai/plugins` repository:

1. **Create the plugin directory structure**
   
   ```bash
   mkdir -p plugins/my-service/skills/my-service/scripts
   mkdir -p plugins/my-service/commands
   ```

2. **Define the plugin manifest**
   
   Copy the structure from [`plugins/zoom/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/zoom/.codex-plugin/plugin.json) and adjust the name, description, and capabilities fields to match your external service.

3. **Write the skill manifest**
   
   Create [`skills/my-service/SKILL.md`](https://github.com/openai/plugins/blob/main/skills/my-service/SKILL.md) with explicit invocation policies:
   
   ```yaml
   policy:
     allow_implicit_invocation: false
   ```

   
   Include input/output schemas and example prompt snippets.

4. **Implement the skill handler**
   
   Create [`skills/my-service/scripts/call_api.py`](https://github.com/openai/plugins/blob/main/skills/my-service/scripts/call_api.py):
   
   ```python
   import os
   import requests
   
   def handler(event: dict) -> dict:
       """
       Expected event shape:
       {
           "url": "https://api.example.com/v1/resource",
           "params": {"q": "search term"}
       }
       """
       api_url = event["url"]
       params = event.get("params", {})
       token = os.getenv("MY_SERVICE_TOKEN")  # Never commit to repo

       
       headers = {"Authorization": f"Bearer {token}"}
       try:
           resp = requests.get(
               api_url, 
               headers=headers, 
               params=params, 
               timeout=10
           )
           resp.raise_for_status()
           data = resp.json()
       except requests.RequestException as e:
           return {"error": str(e), "status": "failed"}
       
       return {"status": "ok", "data": data}
   ```

5. **Configure the runner**
   
   Update [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) to point to your script via the [`agents/openai.yaml`](https://github.com/openai/plugins/blob/main/agents/openai.yaml) runner configuration, mapping skill invocations to the handler function.

6. **Add optional commands**
   
   For multi-step workflows, create a command file in `commands/` following the format of [`plugins/zoom/commands/build-zoom-rest-api-app.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/commands/build-zoom-rest-api-app.md) to chain authentication and API calls.

7. **Write tests**
   
   Verify request formation, error handling, and output shapes by adding tests in `skills/my-service/tests/`, mirroring the structure in `plugins/zoom/skills/rest-api/tests/`.

8. **Commit and sync**
   
   Push to the repository; Codex discovers the plugin automatically once the repository syncs.

## Security Best Practices

When you integrate external services with an OpenAI plugin, credential isolation is mandatory. The reference implementation in [`plugins/zoom/skills/rest-api/examples/webhook-server.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/rest-api/examples/webhook-server.md) demonstrates loading secrets from `.env` files rather than hardcoding them or exposing them to the LLM.

**Key security requirements:**

- **Explicit invocation only**: Set `policy.allow_implicit_invocation: false` in every [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) to prevent unintended API calls
- **Environment variable isolation**: Store `API_TOKEN`, `CLIENT_SECRET`, and OAuth credentials in `.env` files excluded from version control
- **Timeout configuration**: Implement 10-second timeouts on all HTTP requests to prevent hanging connections
- **Error sanitization**: Return structured error objects rather than raw stack traces to the LLM

## Summary

- **Plugin manifests** ([`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json)) declare capabilities and entry points for Codex discovery
- **Explicit-only skills** handle external HTTP requests through isolated Python/Node scripts that read credentials from `.env` files
- **Commands** provide deterministic workflows that chain multiple skills for complex integrations
- **Reviewer agents** audit code for security vulnerabilities before deployment
- The Zoom plugin in `plugins/zoom/` provides a battle-tested blueprint for REST API integrations, webhook handling, and OAuth flows

## Frequently Asked Questions

### How do I prevent the LLM from calling external APIs accidentally?

Set `policy.allow_implicit_invocation: false` in your [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) manifest. This forces users to explicitly reference the skill (e.g., `$rest-api`) rather than allowing the model to invoke it automatically during conversation. According to the `openai/plugins` source code, this explicit-only policy ensures every external service call is intentional and auditable.

### Where should I store API keys and OAuth tokens?

Store sensitive credentials in a `.env` file at the plugin root, never in the repository or skill manifests. The implementation scripts in `plugins/zoom/skills/rest-api/examples/` load tokens via `os.getenv()` at runtime, keeping secrets isolated from the LLM context and version control.

### Can I use languages other than Python for skill implementations?

While Python is the primary language shown in the repository (see [`call_api.py`](https://github.com/openai/plugins/blob/main/call_api.py) patterns in the Zoom plugin), the framework supports any executable via the runner configuration in [`agents/openai.yaml`](https://github.com/openai/plugins/blob/main/agents/openai.yaml). Node.js, Go, or shell scripts are valid alternatives provided they accept JSON input via stdin or event parameters and return JSON-serializable output.

### How do I handle rate limiting from external APIs?

Implement retry logic with exponential backoff in your skill's handler function, similar to the error handling patterns in `plugins/zoom/skills/rest-api/`. Return structured error objects like `{"error": "Rate limit exceeded", "retry_after": 60}` so commands can implement fallback strategies or notify users appropriately.