API Bridge Agent
About The Project
The API Bridge Agent project provides a Tyk middleware plugin that allows users to interact with traditional REST APIs using natural language. It acts as a translator between human language and structured API requests/responses.
Key features:
Converts natural language queries into valid API requests based on OpenAPI specifications.
Transforms API responses back into natural language explanations.
Integrates with Tyk API Gateway as a plugin.
Uses Azure OpenAI’s GPT models for language processing.
Preserves API schema validation and security while enabling conversational interfaces.
This enables developers to build more accessible and user-friendly API interfaces without modifying the underlying API implementations.
Getting Started
Prerequisites
To build the plugin you need the following dependenccies:
Go
CMake
Git
jq
Get the source code by running the following command:
git clone https://github.com/agntcy/api-bridge-agnt
Tyk requires also a Redis database. Deploy it with the following command:
make start_redis
Local development
Built with:
Search for the semantic router.
Tyk for the gateway. We use these dependencies inside the project. However, you don’t need to download it or to build it, everything is managed by the Makefile.
Set environment variables
For OpenAI:
export OPENAI_API_KEY=REPLACE_WITH_YOUR_KEY
export OPENAI_MODEL=gpt-4o-mini
For Azure OpenAI:
export OPENAI_API_KEY=REPLACE_WITH_YOUR_KEY
export OPENAI_ENDPOINT=https://REPLACE_WITH_YOUR_ENDPOINT.openai.azure.com
export OPENAI_MODEL=gpt-4o-mini
Build the Plugin and Start Tyk Locally on Tyk
Dependencies are managed so that you can just run:
make start_tyk
This will automatically build “Tyk”, “search” and the plugin, then install the plugin and start Tyk gateway.
Load and Configure Tyk with an Example API (httpbin.org)
make load_plugin
Other installation
Linux
For Linux (Ubuntu) you can use:
TARGET_OS=linux TARGET_ARCH=amd64 SEARCH_LIB=libllama_go.so make start_tyk
Individual Steps for Building if Needed:
If you need to decompose each task individually, you can split into:
make build_tyk # build tyk
make build_search_lib # build the "search" library, used as semantic router
make build_plugin # build the plugin
make install_plugin # Install the plugin
Tyk Configuration
This plugin relies on Tyk OAS API Definition.
To use it, you need to add the plugin to the postPlugins
and responsePlugins
sections of the x-tyk-api-gateway
section:
"x-tyk-api-gateway": {
"middleware": {
"global": {
"pluginConfig": {
"data": {
"enabled": true,
"value": {
}
},
"driver": "goplugin"
},
"postPlugins": [
{
"enabled": true,
"functionName": "SelectAndRewrite",
"path": "middleware/agent-bridge-plugin.so"
},
{
"enabled": true,
"functionName": "RewriteQueryToOas",
"path": "middleware/agent-bridge-plugin.so"
}
],
"responsePlugins": [
{
"enabled": true,
"functionName": "RewriteResponseToNl",
"path": "middleware/agent-bridge-plugin.so"
}
]
}
}
}
Then add the your OpenAPI specification:
For example, adding the httpbin.org service can be done using the configs/httpbin.org.oas.json
file.
curl http://localhost:8080/tyk/apis/oas \
--header "x-tyk-authorization: foo" \
--header 'Content-Type: text/plain' \
-d@configs/httpbin.org.oas.json
curl http://localhost:8080/tyk/reload/group \
--header "x-tyk-authorization: foo"
It’s then possible to do a query like this:
curl http://localhost:8080/httpbin/json \
--header "X-Nl-Query-Enabled: yes" \
--header "X-Nl-Response-Type: nl" \
--header "Content-Type: text/plain" \
-d "Hello"
In this example http://localhost:8080/httpbin/json
:
“/httpbin/” is the listen path defined on the x-tyk-api-gateway part of the spec
“json” is the endpoint on the spec
Select and Rewrite Middleware
The first middleware function (SelectAndRewrite
) is responsible for selecting
the appropriate OpenAPI endpoint based on the request, and then rewriting the
request to match the expected API format.
The content type for this request should be application/nlq
.
Example:
curl 'http://localhost:8080/github/' \
--header 'Content-Type: application/nlq' \
-d 'List the first issue for the repository named tyk owned by TykTechnologies with the label bug'
Rewrite Query
The second middleware function (RewriteQueryToOas
) is only responsible for
converting the natural language query into a valid API request based on the
selected OpenAPI endpoint.
Important
Here you MUST provide the full path of the target API in the request URL. Rewriting the query will be available only if the content type is not set or is text/plain
Two headers are available for this request:
X-Nl-Query-Enabled:
yes
orno
(default isno
), to enable or disable the natural language query processingX-Nl-Response-Type:
nl
orupstream
(default isupstream
), to select the response format.nl
will return the response in natural language, whileupstream
will return the original API response.
Example:
curl 'http://localhost:8080/gmail/gmail/v1/users/me/messages/send' \
--header "Authorization: Bearer YOUR_GOOGLE_TOKEN" \
--header 'Content-Type: text/plain' \
--header 'X-Nl-Query-Enabled: yes' \
--header 'X-Nl-Response-Type: nl' \
--data 'Send an email to "john.doe@example.com". Explain that we are accepting his offer for Agntcy'
In this example “http://localhost:8080/gmail/gmail/v1/users/me/messages/send”:
/gmail/
is the listen path defined on the x-tyk-api-gateway part of the specgmail/v1/users/me/messages/send
is the endpoint in the specification
Rewrite Response
The third middleware function (RewriteResponseToNl
) is responsible for
converting the API response into natural language.
It can be used standalone or in combination with the RewriteQueryToOas
middleware.
An Example with Github
The configs/api.github.com.gist.deref.oas.json
file is a subset of the Github API, already configured with a few x-nl-input-examples
:
curl 'http://localhost:8080/github/' \
--header 'Content-Type: application/nlq' \
-d 'List the first issue for the repository named tyk owned by TykTechnologies with the label bug'
An Example with Sendgrid API
As a usage example, we will use the API Bridge Agnt to send email via SENGRID API.
Prerequisites
Get an API Key for free from sendgrid sengrid by twilio.
Retreive the open api spec here tsg_mail_v3.json.
Make sure redis is running (otherwise, use
make start_redis
).Make sure you properly export
OPENAI_*
parameters.Start the plugin as described on “Getting Started” section.
Update the API with tyk middleware settings
Configure Tyk to use the sendgrid API by adding the x-tyk-api-gateway
extension:
{
"x-tyk-api-gateway": {
"info": {
"id": "tyk-sendgrid-id",
"name": "Sendgrid Mail API",
"state": {
"active": true
}
},
"upstream": {
"url": "https://api.sendgrid.com"
},
"server": {
"listenPath": {
"value": "/sendgrid/",
"strip": true
}
},
"middleware": {
"global": {
"pluginConfig": {
"data": {
"enabled": true,
"value": {
}
},
"driver": "goplugin"
},
"postPlugins": [
{
"enabled": true,
"functionName": "SelectAndRewrite",
"path": "middleware/agent-bridge-plugin.so"
},
{
"enabled": true,
"functionName": "RewriteQueryToOas",
"path": "middleware/agent-bridge-plugin.so"
}
],
"responsePlugins": [
{
"enabled": true,
"functionName": "RewriteResponseToNl",
"path": "middleware/agent-bridge-plugin.so"
}
]
}
}
}
}
You have an example of a such configuration in configs/api.sendgrid.com.oas.json
Configure an Endpoint to Allow Plugin to Retrieve It
On the same oas file, add a x-nl-input-examples
element to an endpoint with
sentence that describe how you can use the endpoint with natural language.
For example:
{
"...": "...",
"paths": {
"...": "...",
"/v3/mail/send": {
"...": "...",
"post": {
"...": "...",
"x-nl-input-examples": [
"Send an message to 'test@example.com' including a joke. Please use emojis inside it.",
"Send an email to 'test@example.com' including a joke. Please use emojis inside it.",
"Tell to 'test@example.com' that his new car is available.",
"Write a profesional email to reject the candidate 'John Doe <test@example.com'"
]
}
}
},
"...": "..."
You have a configuration example here: ./configs/api.sendgrid.com.oas.json
Add the API to tyk configuration
Your OAS API is ready to be integrated on the Tyk plugin:
curl http://localhost:8080/tyk/apis/oas \
--header "x-tyk-authorization: foo" \
--header 'Content-Type: text/plain' \
-d@configs/api.sendgrid.com.oas.json
curl http://localhost:8080/tyk/reload/group \
--header "x-tyk-authorization: foo"
Test It!
Replace “agntcy@example.com” with a sender email you have configured on your sendgrid account.
curl http://localhost:8080/sendgrid/ \
--header "Authorization: Bearer $SENDGRID_API_KEY" \
--header 'Content-Type: application/nlq' \
-d 'Send a message from me (agntcy@example.com) to John Die <j.doe@example.com>. John is french, the message should be a joke using a lot of emojis, something fun about comparing France and Italy'
As a result, the receiver (j.doe@example.com) must receive a mail like:
Subject: A Little Joke for You! 🇫🇷🇮🇹
Hey John! 😄
I hope you're having a fantastic day! I just wanted to share a little joke with you:
Why did the French chef break up with the Italian chef? 🍽️❤️
Because he couldn't handle all the pasta-bilities! 🍝😂
But don't worry, they still have a "bready" good friendship! 🥖😜
Just remember, whether it's croissants or cannoli, we can all agree that food brings us together! 🍷🍰
Take care and keep smiling! 😊
Best,
agntcy
An example by adding a new API from scratch
Let’s say you want to add a new API to your Tyk API Gateway, and make it available over natural language. Here are the steps:
Create or get the OpenAPI specification for your API.
Add the
x-tyk-api-gateway
section to your OpenAPI specification.Add the
x-nl-input-examples
section to your OpenAPI operations.Configure Tyk to use this new API.
Query your new API with natural language questions.
Let’s choose an API
Let’s choose https://openskynetwork.github.io/opensky-api. There is no OpenAPI specification available so we create one.
OpenSky Network OpenAPI (Click to expand)
{
"openapi": "3.0.0",
"info": {
"title": "OpenSky Network API",
"description": "API for accessing flight tracking data from the OpenSky Network",
"version": "1.0.0"
},
"servers": [
{
"url": "https://opensky-network.org/api"
}
],
"paths": {
"/states/all": {
"get": {
"operationId": "getStatesAll",
"summary": "Get all state vectors",
"description": "Retrieve any state vector of the OpenSky Network",
"parameters": [
{
"name": "time",
"in": "query",
"schema": {
"type": "integer"
},
"description": "Unix timestamp to retrieve states for"
},
{
"name": "icao24",
"in": "query",
"schema": {
"type": "string"
},
"description": "ICAO24 transponder address in hex"
},
{
"name": "lamin",
"in": "query",
"schema": {
"type": "number",
"format": "float"
},
"description": "Lower bound for latitude"
},
{
"name": "lomin",
"in": "query",
"schema": {
"type": "number",
"format": "float"
},
"description": "Lower bound for longitude"
},
{
"name": "lamax",
"in": "query",
"schema": {
"type": "number",
"format": "float"
},
"description": "Upper bound for latitude"
},
{
"name": "lomax",
"in": "query",
"schema": {
"type": "number",
"format": "float"
},
"description": "Upper bound for longitude"
},
{
"name": "extended",
"in": "query",
"schema": {
"type": "integer"
},
"description": "Set to 1 to include aircraft category"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StateVector"
}
}
}
}
}
}
},
"/states/own": {
"get": {
"operationId": "getStatesOwn",
"summary": "Get own state vectors",
"description": "Retrieve state vectors for authenticated user's sensors",
"security": [
{
"basicAuth": []
}
],
"parameters": [
{
"name": "time",
"in": "query",
"schema": {
"type": "integer"
}
},
{
"name": "icao24",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "serials",
"in": "query",
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/StateVector"
}
}
}
}
}
}
},
"/flights/all": {
"get": {
"operationId": "getFlightsAll",
"summary": "Get flights in time interval",
"parameters": [
{
"name": "begin",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "end",
"in": "query",
"required": true,
"schema": {
"type": "integer"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Flight"
}
}
}
}
}
}
}
},
"/flights/aircraft": {
"get": {
"operationId": "getFlightsAircraft",
"summary": "Get flights by aircraft",
"description": "Retrieve flights for a particular aircraft within a time interval",
"parameters": [
{
"name": "icao24",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "Unique ICAO 24-bit address of the transponder in hex string representation (lower case)"
},
{
"name": "begin",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "Start of time interval as Unix timestamp (seconds since epoch)"
},
{
"name": "end",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "End of time interval as Unix timestamp (seconds since epoch)"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Flight"
}
}
}
}
},
"404": {
"description": "No flights found for the given time period"
}
}
}
},
"/flights/arrival": {
"get": {
"operationId": "getFlightsArrival",
"summary": "Get arrivals by airport",
"description": "Retrieve flights that arrived at a specific airport within a given time interval",
"parameters": [
{
"name": "airport",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "ICAO identifier for the airport"
},
{
"name": "begin",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "Start of time interval as Unix timestamp (seconds since epoch)"
},
{
"name": "end",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "End of time interval as Unix timestamp (seconds since epoch)"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Flight"
}
}
}
}
},
"404": {
"description": "No flights found for the given time period"
}
}
}
},
"/flights/departure": {
"get": {
"operationId": "getFlightsDeparture",
"summary": "Get departures by airport",
"description": "Retrieve flights that departed from a specific airport within a given time interval",
"parameters": [
{
"name": "airport",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "ICAO identifier for the airport (usually upper case)"
},
{
"name": "begin",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "Start of time interval as Unix timestamp (seconds since epoch)"
},
{
"name": "end",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "End of time interval as Unix timestamp (seconds since epoch)"
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Flight"
}
}
}
}
},
"404": {
"description": "No flights found for the given time period"
}
}
}
},
"/tracks": {
"get": {
"operationId": "getTracks",
"summary": "Get track by aircraft",
"description": "Retrieve the trajectory for a certain aircraft at a given time. The trajectory is a list of waypoints\ncontaining position, barometric altitude, true track and an on-ground flag.\nNote: This endpoint is experimental.\n",
"parameters": [
{
"name": "icao24",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "Unique ICAO 24-bit address of the transponder in hex string representation (lower case)"
},
{
"name": "time",
"in": "query",
"required": true,
"schema": {
"type": "integer"
},
"description": "Unix timestamp. Can be any time between start and end of a known flight. If time = 0, returns live track if there is any ongoing flight."
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Track"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"StateVector": {
"type": "object",
"properties": {
"time": {
"type": "integer"
},
"states": {
"type": "array",
"items": {
"type": "array",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "number"
},
{
"type": "boolean"
},
{
"type": "array",
"items": {
"type": "integer"
}
}
]
}
}
}
}
},
"Flight": {
"type": "object",
"properties": {
"icao24": {
"type": "string"
},
"firstSeen": {
"type": "integer"
},
"lastSeen": {
"type": "integer"
},
"callsign": {
"type": "string"
}
}
},
"Track": {
"type": "object",
"properties": {
"icao24": {
"type": "string",
"description": "Unique ICAO 24-bit address of the transponder in lower case hex string"
},
"startTime": {
"type": "integer",
"description": "Time of the first waypoint in seconds since epoch"
},
"endTime": {
"type": "integer",
"description": "Time of the last waypoint in seconds since epoch"
},
"callsign": {
"type": "string",
"nullable": true,
"description": "Callsign (8 characters) that holds for the whole track"
},
"path": {
"type": "array",
"description": "Waypoints of the trajectory",
"items": {
"type": "array",
"minItems": 6,
"maxItems": 6,
"items": {
"oneOf": [
{
"type": "integer"
},
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
},
{
"type": "number"
},
{
"type": "boolean"
}
]
}
}
}
}
}
},
"securitySchemes": {
"basicAuth": {
"type": "http",
"scheme": "basic"
}
}
}
}
Update the API and configure Tyk
Let’s add the x-tyk-api-gateway
extension. You can find more information about
it in the Tyk OAS API Definition.
"x-tyk-api-gateway": {
"info": {
"id": "tyk-opensky-network-id",
"name": "OpenSky Network API",
"state": {
"active": true
}
},
"upstream": {
"url": "https://opensky-network.org/api"
},
"server": {
"listenPath": {
"value": "/opensky/",
"strip": true
}
},
"middleware": {
"global": {
"pluginConfig": {
"data": {
"enabled": true,
"value": {}
},
"driver": "goplugin"
},
"postPlugins": [
{
"enabled": true,
"functionName": "SelectAndRewrite",
"path": "middleware/agent-bridge-plugin.so"
},
{
"enabled": true,
"functionName": "RewriteQueryToOas",
"path": "middleware/agent-bridge-plugin.so"
}
],
"responsePlugins": [
{
"enabled": true,
"functionName": "RewriteResponseToNl",
"path": "middleware/agent-bridge-plugin.so"
}
]
}
}
},
Configure Tyk with this new API
curl http://localhost:8080/tyk/apis/oas \
--header "x-tyk-authorization: foo" \
--header 'Content-Type: text/plain' \
-d@configs/opensky_network.json
curl http://localhost:8080/tyk/reload/group --header "x-tyk-authorization: foo"
Tyk is now listening on the
/opensky/
path.You can now query the
/opensky/
path using a natural language query, by adding theapplication/nlq
HTTP Content-Type header, and your query as body.
curl http://localhost:8080/opensky/ \
--header 'Content-Type: application/nlq' \
-d 'Get flights from 12pm to 1pm on March 11th 2025'
Here are the flights that were recorded between 12 PM and 1 PM on March 11th, 2025:
1. **Flight DESET** (ICAO24: 3d34ab)
- Departure Airport: EDEN
- Arrival Airport: EDVK
- First Seen: 12:54 PM
- Last Seen: 1:03 PM
- Horizontal Distance from Departure Airport: 9,544 meters
- Vertical Distance from Departure Airport: 1,181 meters
2. **Flight THD120** (ICAO24: 88530e)
- Departure Airport: VTBS
- Arrival Airport: Not specified
- First Seen: 12:47 PM
- Last Seen: 12:54 PM
- Horizontal Distance from Departure Airport: 2,237 meters
- Vertical Distance from Departure Airport: 432 meters
[...]
Here is what happened behind the scenes:
Content-Type Detection: The system recognizes the
application/nlq
content type.Operation Matching:
Compares your query (“Get flights from 12pm to 1pm on March 11th 2025”) against the
x-nl-input-examples
or the operation description.Identifies the closest matching operation (
getFlightsAll
in this case, ieGET /flights/all
).
Parameter Extraction:
An LLM extracts relevant parameters from your query.
Builds a proper API request.
Request Transformation:
Converts the natural query to a proper HTTP request.
Forwards the request to the upstream API (
GET /flights/all?start=1678560000&end=1678563600
for example).
Response Handling:
Receives the raw API response.
Returns the JSON response with the flight data.
Without any code you were able to query the OpenSky Network API and retrieve flight data.
Improving the Operation Matching
When the description
field provided in the operation definition are poorly
written or missing, it’s possible to improve the selection of the operation by
providing examples of queries that should match the operation.
This is done by adding the x-nl-input-examples
field to the operation definition.
For example, you could provide the following examples which could help improve the operation matching:
In the OpenAPI specification, add the x-nl-output-examples
field to the operation definition:
123{
124"paths": {
125 "/flights/all": {
126 "get": {
127 "operationId": "getFlightsAll",
128 "summary": "Get flights in time interval",
129 "x-nl-input-examples": [
130 "Get flights from 12pm to 1pm on March 11th 2025",
131 "Donne moi les vols de 12h à 13h le 11 mars 2025",
132 "List of flights from 12pm to 1pm on March 11th 2025"
133 ]
134 "parameters": [
135 {
136 "name": "begin",
137 "in": "query",
138 "required": true,
139 "schema": {
140 "type": "integer"
141 }
142 },
143 {
144 "name": "end",
145 "in": "query",
146 "required": true,
147 "schema": {
148 "type": "integer"
149 }
150 }
151 ],
152 "responses": {
153 "200": {
154 "description": "Successful response",
155 "content": {
156 "application/json": {
157 "schema": {
158 "type": "array",
159 "items": {
160 "$ref": "#/components/schemas/Flight"
161 }
162 }
163 }
164 }
165 }
166 }
167 }
168 }
169}