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

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, 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)

Every plugin requires a machine-readable manifest located at .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 serves as the canonical reference, defining skills, commands, and assets that Codex can discover.

Explicit-Only Skills (SKILL.md)

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

  • Declarative metadata in 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 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 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 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

    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 and adjust the name, description, and capabilities fields to match your external service.

  3. Write the skill manifest

    Create skills/my-service/SKILL.md with explicit invocation policies:

    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:

    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 to point to your script via the 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 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 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 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) 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 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 patterns in the Zoom plugin), the framework supports any executable via the runner configuration in 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.

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 →