# How to Handle API Calls from an OpenAI Plugin: A Complete Technical Guide

> Learn to handle API calls from OpenAI plugins with this technical guide. Discover how ChatGPT constructs requests, uses bearer token authentication, and expects JSON responses.

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

---

**OpenAI plugins handle API calls through a stateless HTTP architecture where ChatGPT reads your OpenAPI specification to construct requests, sends them with Bearer token authentication, and expects JSON responses matching your defined schemas.**

OpenAI plugins extend ChatGPT's capabilities by exposing secure web APIs that the model invokes during conversations. To handle API calls from an OpenAI plugin correctly, you must implement a stateless HTTP server that validates authentication, enforces the OpenAPI contract, and returns properly formatted JSON. This guide examines the reference implementation in the `openai/plugins` repository, specifically analyzing how the Figma plugin processes incoming requests at [`plugins/figma/server.ts`](https://github.com/openai/plugins/blob/main/plugins/figma/server.ts).

## Plugin Architecture Overview

Every OpenAI plugin consists of three core components that work together to enable ChatGPT to call external APIs safely.

### The Plugin Manifest

The [`manifest.json`](https://github.com/openai/plugins/blob/main/manifest.json) file declares your plugin's metadata, authentication method, and the location of its OpenAPI specification. Located at [`plugins/figma/manifest.json`](https://github.com/openai/plugins/blob/main/plugins/figma/manifest.json) in the reference repo, this file tells ChatGPT where to find your API documentation and what type of security flow to use (OAuth or API key).

### The OpenAPI Specification

The [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml) file provides a machine-readable description of each endpoint, including paths, parameters, request/response schemas, and security requirements. ChatGPT uses this specification located at [`plugins/figma/openapi.yaml`](https://github.com/openai/plugins/blob/main/plugins/figma/openapi.yaml) to understand exactly how to construct HTTP requests to your server, including which fields are required and what data types to send.

### The Server Implementation

Your server code handles the incoming HTTP requests, performs authentication, executes business logic, and returns JSON conforming to the spec. The reference implementation at [`plugins/figma/server.ts`](https://github.com/openai/plugins/blob/main/plugins/figma/server.ts) uses Express.js, though you can use any web framework.

## How API Calls Flow from ChatGPT to Your Server

When a user asks ChatGPT to perform an action your plugin supports, the following stateless flow occurs:

1. **Model Decision** – ChatGPT parses the user's intent and consults your OpenAPI spec to determine which endpoint to call and what parameters to include.

2. **Request Construction** – The model constructs an HTTP request with the correct method, URL, headers, and JSON body based on the schema defined in [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml).

3. **Authentication Header** – ChatGPT adds an `Authorization: Bearer <token>` header (the token type depends on your manifest's `authentication` setting, either `ON_INSTALL` for OAuth or `ON_USE` for API keys).

4. **HTTP Execution** – The request is sent directly from ChatGPT's backend to your plugin's server endpoint, such as `POST https://my-plugin.example.com/v1/frames`.

5. **Response Processing** – Your server returns a JSON response matching the OpenAPI schema, which ChatGPT parses and incorporates into its reply to the user.

## Implementing Secure API Endpoints

Your server must verify the Bearer token on every request and validate incoming payloads against your OpenAPI schema. Below is a minimal Express.js implementation based on [`plugins/figma/server.ts`](https://github.com/openai/plugins/blob/main/plugins/figma/server.ts).

```typescript
import express from "express";
import cors from "cors";
import helmet from "helmet";
import bodyParser from "body-parser";
import { verifyToken } from "./auth";

const app = express();
app.use(cors());
app.use(helmet());
app.use(bodyParser.json());

app.post("/v1/frames", async (req, res) => {
  // Authentication check
  const authHeader = req.headers["authorization"];
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing Bearer token" });
  }
  
  const token = authHeader.split(" ")[1];
  const user = await verifyToken(token);
  if (!user) {
    return res.status(403).json({ error: "Invalid token" });
  }

  // Payload validation matching OpenAPI schema
  const { name, width, height } = req.body;
  if (!name || typeof width !== "number" || typeof height !== "number") {
    return res.status(400).json({ error: "Invalid payload" });
  }

  // Business logic execution
  try {
    const frameId = await createFigmaFrame(user, { name, width, height });
    return res.status(200).json({ id: frameId, name, width, height });
  } catch (e) {
    console.error(e);
    return res.status(502).json({ error: "Failed to create frame" });
  }
});

const PORT = process.env.PORT ?? 8080;
app.listen(PORT, () => console.log(`Plugin server listening on ${PORT}`));

```

**Key implementation details:**

- **CORS and Helmet** – Required to allow requests from the ChatGPT frontend and set security headers.
- **Strict validation** – The server checks that `width` and `height` are numbers and `name` exists, mirroring the constraints in [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml).
- **Stateless design** – Each request contains all necessary context; the server does not maintain session state between calls.

## Authentication and Token Verification

For plugins using `ON_INSTALL` authentication (OAuth), the token is a JWT signed by OpenAI. Create a verification helper like [`plugins/figma/auth.ts`](https://github.com/openai/plugins/blob/main/plugins/figma/auth.ts) to validate these tokens using RS256:

```typescript
import jwt from "jsonwebtoken";

const PUBLIC_KEY = process.env.OPENAI_PLUGIN_JWK; // JWK supplied by OpenAI on install

export async function verifyToken(token: string) {
  try {
    const payload = jwt.verify(token, PUBLIC_KEY, { algorithms: ["RS256"] });
    return payload as { sub: string; scopes: string[] };
  } catch (err) {
    console.warn("Invalid token:", err);
    return null;
  }
}

```

The payload typically contains the user's OpenAI ID (`sub`) and authorized scopes. Always verify the algorithm is RS256 to prevent algorithm confusion attacks.

## Error Handling and Response Standards

Return consistent HTTP status codes and JSON error objects so ChatGPT can surface meaningful messages to users. Implement an Express error handler:

```typescript
app.use((err, _req, res, _next) => {
  console.error(err);
  res.status(500).json({ error: "Internal server error" });
});

```

For client errors, return `4xx` status codes with a descriptive `error` field in the JSON body. For server failures, use `5xx` codes. This allows the model to explain failures (e.g., "I couldn't create the frame because the service is down").

## Rate Limiting and Versioning

Protect your backend by implementing rate limiting (e.g., using `express-rate-limit`) to prevent abuse. Additionally, include an `apiVersion` field in your [`manifest.json`](https://github.com/openai/plugins/blob/main/manifest.json) and version your URL paths (e.g., `/v1/frames`) so you can evolve your API without breaking existing ChatGPT integrations.

## Summary

- **Three files define the contract**: [`manifest.json`](https://github.com/openai/plugins/blob/main/manifest.json) for metadata, [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml) for request/response schemas, and [`server.ts`](https://github.com/openai/plugins/blob/main/server.ts) for implementation.
- **Stateless HTTP**: Each API call from ChatGPT is independent and must include full context and authentication.
- **Security first**: Verify Bearer tokens (JWTs for OAuth, API keys for `ON_USE`) on every request using RS256 verification.
- **Schema enforcement**: Validate incoming payloads against your OpenAPI specification and return JSON that strictly matches your defined response schemas.
- **Error transparency**: Return proper HTTP status codes and JSON error objects to enable ChatGPT to explain failures to users.

## Frequently Asked Questions

### What authentication method should I use for my OpenAI plugin?

Choose `ON_INSTALL` (OAuth) for user-specific data requiring consent, or `ON_USE` (API key) for simple, application-wide access. The `ON_INSTALL` flow provides a JWT token that your server verifies using the public key from OpenAI, as shown in [`plugins/figma/auth.ts`](https://github.com/openai/plugins/blob/main/plugins/figma/auth.ts).

### How does ChatGPT know which parameters to send to my API?

ChatGPT reads your [`openapi.yaml`](https://github.com/openai/plugins/blob/main/openapi.yaml) specification to understand your endpoints' request schemas. The model extracts parameter names, data types, and required fields from this specification to construct valid HTTP requests automatically.

### What happens if my plugin server returns an error?

ChatGPT receives the HTTP status code and JSON error body from your server. If you return a `4xx` or `5xx` status with a JSON object containing an `error` field, the model will surface that message to the user (e.g., "I couldn't complete the action because your API quota is exceeded").

### Do I need to use Express.js, or can I use another framework?

You can use any web framework (Fastify, Flask, Django, etc.) as long as it handles HTTP requests, supports CORS headers for the ChatGPT frontend, and returns JSON responses matching your OpenAPI specification. The `openai/plugins` repository uses Express.js as a reference implementation, but the architectural patterns apply universally.