Proxy LLM provider request
Verifies the agent's signature and project, runs the gateway's plugins, records the exchange for audit, and forwards the request, with the provider credentials the agent sent, to the provider's configured upstream. The path after the prefix is the provider's own API path: /v1/anthropic_api forwards to the Anthropic API, /v1/openai_api to the OpenAI API, and /v1/chatgpt_backend to the ChatGPT backend API, by default https://api.anthropic.com, https://api.openai.com, and https://chatgpt.com/backend-api. For example, POST /v1/anthropic_api/v1/messages calls the Anthropic Messages API. The provider's response, including streams, passes through. Request bodies are capped at the gateway's limit, 32 MiB by default. A plugin denial carries the plugin's status, usually 403 or 503.
https://<host>/v1/chatgpt_backend/{path}curl --request PATCH \
--url 'https://<host>/v1/chatgpt_backend/{path}' \
--header 'Signature: <api-key>'const response = await fetch("https://<host>/v1/chatgpt_backend/{path}", {
method: "PATCH",
headers: {
"Signature": "<api-key>"
}
});
const data = await response.json();const response = await fetch("https://<host>/v1/chatgpt_backend/{path}", {
method: "PATCH",
headers: {
"Signature": "<api-key>"
}
});
if (!response.ok) throw new Error(`${response.status} ${response.statusText}`);
const data: string = await response.json();import requests
response = requests.patch(
"https://<host>/v1/chatgpt_backend/{path}",
headers={
"Signature": "<api-key>",
},
)
response.raise_for_status()
print(response.json())package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, err := http.NewRequest(http.MethodPatch, "https://<host>/v1/chatgpt_backend/{path}", nil)
if err != nil {
panic(err)
}
req.Header.Set("Signature", "<api-key>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
fmt.Println(res.Status, string(data))
}import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://<host>/v1/chatgpt_backend/{path}"))
.header("Signature", "<api-key>")
.method("PATCH", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Patch, "https://<host>/v1/chatgpt_backend/{path}");
request.Headers.Add("Signature", "<api-key>");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");<?php
$ch = curl_init('https://<host>/v1/chatgpt_backend/{path}');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Signature: <api-key>',
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status . " " . $response . PHP_EOL;require "net/http"
uri = URI("https://<host>/v1/chatgpt_backend/{path}")
request = Net::HTTP::Patch.new(uri)
request["Signature"] = "<api-key>"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
http.request(request)
end
puts "#{response.code} #{response.body}"// Cargo.toml dependencies: reqwest, tokio (features "full")
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::Client::new()
.patch("https://<host>/v1/chatgpt_backend/{path}")
.header("Signature", "<api-key>")
.send()
.await?;
println!("{} {}", response.status(), response.text().await?);
Ok(())
}Authorizations
AgentSignature· API key in header "Signature" — An RFC 9421 HTTP message signature by a registered agent's Ed25519 or P-256 key, sent with its Signature-Input header. The keyid is the agent's registered DID, created must fall within the signature's maximum age and clock skew, and the covered components start with @method and @target-uri. A request with a body also covers content-digest. Proxy and relay requests cover x-viper-nonce, x-viper-timestamp, and either x-viper-state-cid (state in an uploaded blob) or x-viper-credential-cid (state in the agent's state attestation credential); credential renewal covers x-viper-nonce. Viper signs every request this way.
Parameters
Path parameters
| Name | Type | Description |
|---|---|---|
pathrequired | string | Provider API path, such as v1/messages or v1/chat/completions |
Responses
200Provider response passthrough
string
"string"401Unauthorized
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}403Forbidden
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}413Request Entity Too Large
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}426Upgrade Required
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}500Internal Server Error
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}502Bad Gateway
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}503Service Unavailable
codestringcorrelation_idstringmessagestring
{
"code": "string",
"correlation_id": "string",
"message": "string"
}