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>.tsautomatically become API routes - Expo Router: Files named
+api.tsinsideapp/<route>/directories define API endpoints - Express: Routes mounted via
app.use('/api/<resource>', ...)inroutes/api/<resource>.js - Cloudflare Workers: Routes defined in
src/routes/<name>.tsreferenced viawrangler.toml
How the Pieces Fit Together
To successfully define API routes in an OpenAI plugin, follow this integration flow:
- Write the OpenAPI definition listing each route, HTTP method, and request/response schemas
- Create the source file implementing the handler using your runtime's conventions
- Export the handler in the format required by your platform (e.g.,
export default function handlerfor Next.js) - Deploy the plugin, allowing the platform to compile source files into serverless functions
- Register the plugin by ensuring
plugin.json'sapi.urlpoints 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: trueinplugin.jsonfor 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
/apiprefixes automatically in some contexts, while Express requires explicit mounting. - Missing authentication checks: If
plugin.jsonmarks a route as authenticated, implement token verification in the handler using patterns fromplugins/zoom/skills/oauth/examples/s2s-oauth-redis.md. - Incorrect file naming: Expo Router strictly requires
+api.ts(not.jsor 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, orroutes/api/) - Reference the OpenAPI specification in
plugin.jsonto enable model discovery of your endpoints - Set
has_user_authenticationappropriately 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →