Authentication
All API requests (except /health and /.well-known/oauth-protected-resource) require a Bearer token in the Authorization header.
Token types
Gunni accepts two kinds of tokens:
gn_live_. Created at gunni.ai/keys. Recommended for server-side and CLI use.Header format
Authorization: Bearer gn_live_your_key_herecurl example
curl https://api.gunni.ai/api/upload-url?filename=photo.jpg&content_type=image/jpeg \
-H "Authorization: Bearer gn_live_your_key_here"Base URL
All endpoints are relative to the base URL for your environment.
| Environment | Base URL |
|---|---|
| Production | https://api.gunni.ai |
| Local dev | http://localhost:3847 |
GET /health
Returns the server status and current version. No authentication required.
Request
curl https://api.gunni.ai/healthResponse
{
"status": "ok",
"version": "0.3.0"
}GET /api/upload-url
Generates a signed upload URL for uploading a local file to Gunni's storage. Use the returned uploadUrl to PUT the file, then pass the publicUrl as an image reference in generation calls.
Requires authentication.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
| filename | string | — | The filename including extension (e.g. photo.jpg). |
| content_type | string | — | MIME type of the file (e.g. image/jpeg, image/png). |
curl example
curl "https://api.gunni.ai/api/upload-url?filename=photo.jpg&content_type=image%2Fjpeg" \
-H "Authorization: Bearer gn_live_your_key_here"Response
{
"uploadUrl": "https://storage.example.com/signed-upload-url?...",
"publicUrl": "https://storage.example.com/user-assets/photo.jpg"
}Upload the file
# Step 1: Get the signed URL
RESP=$(curl -s "https://api.gunni.ai/api/upload-url?filename=photo.jpg&content_type=image%2Fjpeg" \
-H "Authorization: Bearer gn_live_your_key_here")
UPLOAD_URL=$(echo $RESP | jq -r .uploadUrl)
PUBLIC_URL=$(echo $RESP | jq -r .publicUrl)
# Step 2: Upload the file
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
# Step 3: Use the public URL in a generation call
echo "File available at: $PUBLIC_URL"Node.js example
const fs = require("fs")
async function uploadFile(localPath, filename, contentType) {
// Get signed URL
const res = await fetch(
`https://api.gunni.ai/api/upload-url?filename=${filename}&content_type=${encodeURIComponent(contentType)}`,
{ headers: { Authorization: "Bearer gn_live_your_key_here" } }
)
const { uploadUrl, publicUrl } = await res.json()
// Upload the file
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": contentType },
body: fs.readFileSync(localPath),
})
return publicUrl
}
const publicUrl = await uploadFile("./photo.jpg", "photo.jpg", "image/jpeg")
console.log("Uploaded:", publicUrl)MCP over HTTP
Gunni's primary interface is the MCP protocol over HTTP (Streamable HTTP transport). Clients send JSON-RPC 2.0 requests to /mcp and receive JSON-RPC responses.
The session lifecycle: initialize (POST) → send tool calls (POST) → close (DELETE). The server returns a Mcp-Session-Id header on initialization which must be sent with every subsequent request in the session.
Endpoints
| Method | Path | Description |
|---|---|---|
| POST | /mcp | Initialize session or send JSON-RPC request |
| DELETE | /mcp | Close the session |
Required headers
| Name | Type | Default | Description |
|---|---|---|---|
| Authorization | string | — | Bearer token: your Gunni API key or Supabase JWT. |
| Content-Type | string | — | Must be application/json. |
| Mcp-Session-Id | string | — | Session ID returned by the initialize call. Required on all calls after initialization. |
Initialize Session
Send the MCP initialize request. The server responds with its capabilities and returns a Mcp-Session-Id header — save this for all subsequent calls.
curl -X POST https://api.gunni.ai/mcp \
-H "Authorization: Bearer gn_live_your_key_here" \
-H "Content-Type: application/json" \
-D - \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "my-app", "version": "1.0.0" }
}
}'The response headers include Mcp-Session-Id: <session-id>. Save this value.
HTTP/2 200
content-type: application/json
mcp-session-id: sess_abc123xyz
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {} },
"serverInfo": { "name": "gunni", "version": "0.3.0" }
}
}Call a Tool
Send a tools/call request with the tool name and arguments. The response contains the tool result.
curl -X POST https://api.gunni.ai/mcp \
-H "Authorization: Bearer gn_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Mcp-Session-Id: sess_abc123xyz" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "image",
"arguments": {
"prompt": "a fox in watercolor style",
"width": 1024,
"height": 1024
}
}
}'{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Generated image: https://storage.gunni.ai/outputs/abc123.png"
}
]
}
}Generation calls are async — the server polls the underlying model API and returns only when the result is ready. Expect latency of 5–60 seconds depending on the model and media type.
Node.js example
async function generateImage(prompt) {
const BASE = "https://api.gunni.ai"
const TOKEN = "gn_live_your_key_here"
// 1. Initialize
const initRes = await fetch(`${BASE}/mcp`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "initialize",
params: {
protocolVersion: "2024-11-05",
capabilities: {},
clientInfo: { name: "my-app", version: "1.0.0" },
},
}),
})
const sessionId = initRes.headers.get("mcp-session-id")
// 2. Call the image tool
const callRes = await fetch(`${BASE}/mcp`, {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Mcp-Session-Id": sessionId,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 2,
method: "tools/call",
params: {
name: "image",
arguments: { prompt, width: 1024, height: 1024 },
},
}),
})
const result = await callRes.json()
return result.result.content[0].text
}
const url = await generateImage("a fox in watercolor style")
console.log(url)Close Session
Send a DELETE request with the session ID to clean up server-side session state. This is optional but recommended for long-running applications.
curl -X DELETE https://api.gunni.ai/mcp \
-H "Authorization: Bearer gn_live_your_key_here" \
-H "Mcp-Session-Id: sess_abc123xyz"Available Tools
All Gunni MCP tools are available via the HTTP API. See the MCP reference for full parameter documentation on each tool.
Generation (consume credits)
| Tool | Description |
|---|---|
| image | Generate, edit, upscale, describe, or remove background from an image |
| video | Generate video from a still image or text prompt |
| audio | Convert text to speech |
| lipsync | Sync audio to a video or animate a portrait image to speak |
Knowledge (free)
| Tool | Description |
|---|---|
| learn | Access the Gunni creative knowledge base |
| models | List available models and capabilities |
| history | Search past generations |
| visual_research | Find reference images and visual inspiration |
Asset Management (free)
| Tool | Description |
|---|---|
| refs, ref_save, upload_ref | Reference image management |
| styles, style_create, style_delete | Visual style management |
| presets, preset_create, preset_delete | Production preset management |
| templates, template_save, template_delete | Prompt template management |
| pipelines, pipeline_save, pipeline_delete | Multi-step workflow management |
OAuth Discovery
Gunni exposes RFC 9728 OAuth protected resource metadata at the well-known endpoint. MCP clients that implement OAuth discovery use this to locate the authorization server.
Request
curl https://api.gunni.ai/.well-known/oauth-protected-resourceResponse
{
"resource": "https://api.gunni.ai",
"authorization_servers": ["https://accounts.gunni.ai"],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://gunni.ai/docs/api"
}Errors
HTTP errors use standard status codes. MCP tool errors use JSON-RPC error objects inside an otherwise successful HTTP 200 response.
HTTP errors
| Status | Meaning |
|---|---|
| 401 | Missing or invalid Authorization header |
| 400 | Malformed request body or missing required parameters |
| 404 | Session not found (invalid or expired Mcp-Session-Id) |
| 500 | Server error |
JSON-RPC error response
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32600,
"message": "Invalid request",
"data": "Missing required parameter: prompt"
}
}Insufficient credits
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Error: Insufficient credits. Please top up at gunni.ai/billing."
}
],
"isError": true
}
}