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

> Learn to define API routes in an OpenAI plugin by creating HTTP endpoints and documenting them in your OpenAPI spec. A complete guide for developers.

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

---

**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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/expo/.codex-plugin/plugin.json), the manifest structure includes:

```json
{
  "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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/plugins/vercel/examples/pages/api/hello.ts):

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

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

```typescript
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`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/oauth/examples/s2s-oauth-redis.md):

```javascript
// 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`](https://github.com/openai/plugins/blob/main/routes/api/users.js):

```javascript
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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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`](https://github.com/openai/plugins/blob/main/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.