How to Define API Routes in an OpenAI Plugin: A Complete Guide

Define API routes in an OpenAI plugin by creating HTTP endpoint handlers in your runtime-specific directory (e.g., pages/api/ for Next.js or app/ for Expo Router), then describing them in an OpenAPI specification referenced by plugin.json.

The openai/plugins repository demonstrates how plugins expose functionality to language models through standard HTTP endpoints. To define API routes in an OpenAI plugin, you implement handler functions using your chosen runtime's conventions and register them via the plugin manifest and OpenAPI specification. This architecture allows the OpenAI model to discover and invoke your endpoints dynamically.

Understanding the Three-Layer Architecture

OpenAI plugins use a three-layer system to expose API routes: the manifest file points to an OpenAPI specification, which describes endpoints that map to source-level handler files.

The Plugin Manifest (plugin.json)

The plugin.json file serves as the entry point. It contains an api object that specifies where to find the OpenAPI specification and whether authentication is required.

In plugins/expo/.codex-plugin/plugin.json, the manifest structure includes:

{
  "api": {
    "type": "openapi",
    "url": "https://my-plugin.vercel.app/openapi.json",
    "has_user_authentication": false
  }
}

The has_user_authentication boolean flag tells the model whether routes require OAuth tokens before invocation.

The OpenAPI Specification

This YAML or JSON document enumerates every available endpoint, HTTP methods, request/response schemas, and authentication requirements. According to plugins/expo/skills/expo-api-routes/SKILL.md, the OpenAPI spec bridges the gap between natural language queries and your concrete API implementations.

Source-Level Route Files by Runtime

The actual handler logic resides in runtime-specific files. The openai/plugins repository supports multiple patterns:

  • Next.js / Vercel: Files in pages/api/<name>.ts automatically become API routes
  • Expo Router: Files named +api.ts inside app/<route>/ directories define API endpoints
  • Express: Routes mounted via app.use('/api/<resource>', ...) in routes/api/<resource>.js
  • Cloudflare Workers: Routes defined in src/routes/<name>.ts referenced via wrangler.toml

How the Pieces Fit Together

To successfully define API routes in an OpenAI plugin, follow this integration flow:

  1. Write the OpenAPI definition listing each route, HTTP method, and request/response schemas
  2. Create the source file implementing the handler using your runtime's conventions
  3. Export the handler in the format required by your platform (e.g., export default function handler for Next.js)
  4. Deploy the plugin, allowing the platform to compile source files into serverless functions
  5. Register the plugin by ensuring plugin.json's api.url points to your hosted OpenAPI document

Implementation Examples by Runtime

Next.js on Vercel

For Vercel deployments, create files in the pages/api/ directory. Each file exports a default handler function that receives NextApiRequest and NextApiResponse objects.

From plugins/vercel/examples/pages/api/hello.ts:

import type { NextApiRequest, NextApiResponse } from 'next'

export default function handler(_req: NextApiRequest, res: NextApiResponse) {
  res.status(200).json({ message: 'Hello from OpenAI plugin!' })
}

Vercel automatically exposes this at /api/hello based on the file path.

Expo Router

Expo Router uses a file-based convention where +api.ts files define API routes. These routes execute server-side, making them safe for environment variable access.

From plugins/expo/examples/app/hello/+api.ts:

import { json } from 'expo-router'

export const GET = async () => {
  return json({ message: 'Hello from Expo Router API route' })
}

For protected secrets, reference plugins/expo/examples/app/secret/+api.ts:

import { json } from 'expo-router'

export const GET = async () => {
  // Server-only environment variable (not prefixed with EXPO_PUBLIC_)
  const secret = process.env.MY_SUPER_SECRET
  if (!secret) {
    return json({ error: 'Missing secret' }, { status: 500 })
  }
  return json({ secret })
}

Express (Zoom Example)

For Express-based plugins, mount routes using middleware patterns. As shown in plugins/zoom/skills/oauth/examples/s2s-oauth-redis.md:

// app.js
const express = require('express')
const app = express()

// Apply token verification middleware to API routes
app.use('/api/users', tokenCheck, require('./routes/api/users'))
app.use('/api/meetings', tokenCheck, require('./routes/api/meetings'))

The corresponding route file at routes/api/users.js:

module.exports = (req, res) => {
  // Business logic with authenticated user context
  res.json({ userId: req.user.id })
}

Authentication and Security Considerations

When you define API routes in an OpenAI plugin that handle sensitive data, implement these security patterns from the openai/plugins source:

  • Set has_user_authentication: true in plugin.json for routes requiring OAuth tokens
  • Use server-side environment variables only in API route files (never expose EXPO_PUBLIC_ prefixed variables for secrets)
  • Reuse authentication middleware across routes, as demonstrated in the Zoom OAuth examples

Common Pitfalls to Avoid

  • Route path mismatches: Ensure your OpenAPI spec paths match the runtime's public URL exactly. Next.js strips /api prefixes automatically in some contexts, while Express requires explicit mounting.
  • Missing authentication checks: If plugin.json marks a route as authenticated, implement token verification in the handler using patterns from plugins/zoom/skills/oauth/examples/s2s-oauth-redis.md.
  • Incorrect file naming: Expo Router strictly requires +api.ts (not .js or other extensions). Verify your runtime's naming conventions in the respective skill documentation.

Summary

  • Define API routes in an OpenAI plugin by creating handler files in runtime-specific directories (pages/api/, app/+api.ts, or routes/api/)
  • Reference the OpenAPI specification in plugin.json to enable model discovery of your endpoints
  • Set has_user_authentication appropriately and implement corresponding security checks
  • Keep environment variables server-side by avoiding client-exposed prefixes in API route files
  • Follow runtime-specific conventions exactly, including file extensions and directory structures

Frequently Asked Questions

Do I need to manually register each route in plugin.json?

No. You register the OpenAPI specification URL in plugin.json, not individual routes. The OpenAPI document at that URL contains the complete list of endpoints. The runtime (Vercel, Expo Router, etc.) automatically discovers and deploys the source files based on its file-system conventions.

Can I use environment variables in API routes?

Yes, but only server-side environment variables without the EXPO_PUBLIC_ prefix. As implemented in plugins/expo/examples/app/secret/+api.ts, API routes execute on the server, allowing secure access to private environment variables. Client-side code cannot access these values.

How does the OpenAI model discover my routes?

The model reads the OpenAPI specification URL defined in plugin.json's api.url field. This JSON or YAML document describes each endpoint's path, method, parameters, and response schemas. The model uses this specification to determine when and how to invoke your API routes during conversations.

What happens if my OpenAPI spec doesn't match my source files?

The model may attempt to call endpoints that return 404 errors or incorrect data. The plugins/expo/skills/expo-api-routes/SKILL.md documentation emphasizes keeping the OpenAPI specification synchronized with your actual route implementations, including path parameters and response schemas, to ensure reliable plugin functionality.

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 →