How to Handle API Calls from an OpenAI Plugin: A Complete Technical Guide
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.
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 file declares your plugin's metadata, authentication method, and the location of its OpenAPI specification. Located at 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 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 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 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:
-
Model Decision – ChatGPT parses the user's intent and consults your OpenAPI spec to determine which endpoint to call and what parameters to include.
-
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. -
Authentication Header – ChatGPT adds an
Authorization: Bearer <token>header (the token type depends on your manifest'sauthenticationsetting, eitherON_INSTALLfor OAuth orON_USEfor API keys). -
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. -
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.
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
widthandheightare numbers andnameexists, mirroring the constraints inopenapi.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 to validate these tokens using RS256:
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:
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 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.jsonfor metadata,openapi.yamlfor request/response schemas, andserver.tsfor 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.
How does ChatGPT know which parameters to send to my API?
ChatGPT reads your 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.
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 →