OpenAPI is a specification for describing HTTP APIs. The OpenAPI Wrapper is a generic WebAssembly (Wasm) foreign data wrapper that can connect to any REST API with an OpenAPI 3.0+ specification.
This wrapper allows you to query any REST API endpoint as a PostgreSQL foreign table, with support for path parameters, pagination, and automatic schema import.
By default, Postgres stores FDW credentials inside pg_catalog.pg_foreign_server in plain text. Anyone with access to this table will be able to view these credentials. Wrappers is designed to work with Vault, which provides an additional level of security for storing credentials. We recommend using Vault to store your credentials.
123456
-- Save your API key in Vault and retrieve the created `key_id`selectvault.create_secret('your-api-key','my_api','API key for My API');
Connecting to an API
We need to provide Postgres with the credentials to access the API and any additional options. We can do this using the create server command:
1 2 3 4 5 6 7 8 910
createservermy_api_serverforeigndatawrapperwasm_wrapperoptions(fdw_package_name'supabase:openapi-fdw',fdw_package_url'{See: "Available Versions"}',fdw_package_checksum'{See: "Available Versions"}',fdw_package_version'{See: "Available Versions"}',base_url'https://api.example.com/v1',api_key_id'<key_ID>'-- The Key ID from Vault);
API endpoint path (e.g., /users, /users/{user_id}/posts).
rowid_column
No
Column used as row identifier for single-resource access and modifications (default: id).
response_path
No
JSON pointer to extract data array from response (e.g., /data, /results).
object_path
No
JSON pointer to extract nested object from each row (e.g., /properties for GeoJSON).
cursor_path
No
JSON pointer to pagination cursor in response.
cursor_param
No
Override server-level cursor parameter name.
page_size_param
No
Override server-level page size parameter name.
page_size
No
Override server-level page size.
Automatic Schema Import
If you provide a spec_url in the server options, you can automatically import table definitions:
1 2 3 4 5 6 7 8 910111213141516
-- Import all endpointsimportforeignschemaopenapifromservermy_api_serverintoapi;-- Import specific endpoints onlyimportforeignschemaopenapilimitto("users","orders")fromservermy_api_serverintoapi;-- Import all except specific endpointsimportforeignschemaopenapiexcept("internal_endpoint")fromservermy_api_serverintoapi;
Note
IMPORT FOREIGN SCHEMA only generates tables for non-parameterized GET endpoints (e.g., /users, /orders). Endpoints with path parameters like /users/{user_id}/posts are skipped because they require WHERE clause values at query time. Create these tables manually using the endpoint option with {param} placeholders — see Path Parameters for examples.
Path Parameters
The OpenAPI FDW supports path parameter substitution. Define parameters in the endpoint template using {param_name} syntax, and provide values via WHERE clauses:
1 2 3 4 5 6 7 8 910111213141516
-- Endpoint template with path parametercreateforeigntableopenapi.user_posts(user_idtext,idtext,titletext,bodytext,attrsjsonb)servermy_api_serveroptions(endpoint'/users/{user_id}/posts',rowid_column'id');-- Query with path parameter - generates GET /users/123/postsselect*fromopenapi.user_postswhereuser_id='123';
Multiple Path Parameters
1 2 3 4 5 6 7 8 910111213141516
createforeigntableopenapi.project_issues(orgtext,repotext,idtext,titletext,statustext,attrsjsonb)servermy_api_serveroptions(endpoint'/projects/{org}/{repo}/issues',rowid_column'id');-- Generates GET /projects/acme/widgets/issuesselect*fromopenapi.project_issueswhereorg='acme'andrepo='widgets';
Query Pushdown
Single Resource Access
When filtering by the rowid_column, the FDW automatically requests a single resource:
12
-- Generates GET /users/user-123select*fromopenapi.userswhereid='user-123';
Query Parameters
Other WHERE clause filters are passed as query parameters:
12
-- Generates GET /users?status=activeselect*fromopenapi.userswherestatus='active';
Columns used as query or path parameters always return the value from the WHERE clause, even if the API response contains the same field with different casing. This ensures PostgreSQL's post-filter always passes.
Pagination
The FDW automatically handles pagination. It supports:
Cursor-based pagination - Uses cursor_param and cursor_path
URL-based pagination - Follows next links in response
Any foreign table can include an attrs column of type jsonb to capture the entire raw JSON response for each row:
1234567
createforeigntableopenapi.users(idtext,nametext,attrsjsonb-- Contains full JSON object)servermy_api_serveroptions(endpoint'/users');
Limitations
Read-only: This FDW only supports SELECT operations. INSERT, UPDATE, and DELETE are not supported at this time.
No transactions: Each SQL statement results in immediate HTTP requests; there is no transactional grouping.
Authentication: Currently supports API Key and Bearer Token authentication. OAuth flows are not supported.
OpenAPI version: Only OpenAPI 3.0+ specifications are supported (not Swagger 2.0).
Rate Limiting
The FDW automatically handles rate limiting:
HTTP 429 responses: Automatically retries up to 3 times
Retry-After header: Respects server-specified delay when provided
Exponential backoff: Falls back to 1s, 2s, 4s delays when no Retry-After header is present
For APIs with very strict rate limits, consider using materialized views to cache results.
Examples
Basic Query
1 2 3 4 5 6 7 8 91011121314151617181920
-- Create a foreign server connecting to the Weather.gov APIcreateserveropenapi_serverforeigndatawrapperwasm_wrapperoptions(fdw_package_url'https://github.com/supabase/wrappers/releases/download/wasm_openapi_fdw_v0.1.4/openapi_fdw.wasm',fdw_package_name'supabase:openapi-fdw',fdw_package_version'0.1.4',fdw_package_checksum'dd434f8565b060b181d1e69e1e4d5c8b9c3ac5ca444056d3c2fb939038d308fe',base_url'https://api.weather.gov',spec_url'https://api.weather.gov/openapi.json');-- Create a schema to hold the imported foreign tablescreateschemaifnotexistsopenapi;-- Auto-import all API endpoints as foreign tables based on the OpenAPI specimportforeignschemaopenapifromserveropenapi_serverintoopenapi;-- Query the stations endpoint to get weather station dataselect*fromopenapi.stationslimit5;
Nested Resources
1 2 3 4 5 6 7 8 91011121314
-- Create a foreign table for a parameterized endpoint with {zone_id} path parametercreateforeigntableopenapi.zone_stations(zone_idtext,idtext,typetext,attrsjsonb)serveropenapi_serveroptions(endpoint'/zones/forecast/{zone_id}/stations',rowid_column'id');-- Query stations for Alaska zone AKZ317 - generates GET /zones/forecast/AKZ317/stationsselectid,typefromopenapi.zone_stationswherezone_id='AKZ317';
-- API returns: {"data": [...], "meta": {...}}createforeigntableopenapi.items(idtext,nametext,attrsjsonb)servermy_api_serveroptions(endpoint'/items',response_path'/data');
Combining with Materialized Views
For frequently accessed data, use materialized views to reduce API calls:
12345678
creatematerializedviewapi_users_cacheasselect*fromopenapi.users;-- Query the cacheselect*fromapi_users_cache;-- Refresh when neededrefreshmaterializedviewapi_users_cache;