Using the HTTP API
Connect your application to Relvo and handle the full request lifecycle.
Base URL and conventions
https://api.relvo.si- Send JSON with
Content-Type: application/json. Bodies are limited to 64 KiB; quotes, executions, discovery and custom tools allow 1 MiB, and uploads and team skills 16 MiB. - Successful responses are
{ "data": … }. Errors are{ "error": … }; see Troubleshooting. - Timestamps are ISO 8601 strings. Money is integer micros of USD, except
GET /tools/usage, which reportsamountCentsin US cents. - Responses of 8 KiB or more are compressed with Brotli or gzip when you send
Accept-Encoding.
Authenticate the request
Create a project key in API keys ↗ with the project:read, tools:read, tools:execute and executions:read scopes, and send it as a bearer credential over HTTPS. Keep it on your server.
export RELVO_API_URL="https://api.relvo.si"
export RELVO_API_KEY="rlv_..."GET /tools/project returns the team and project the key belongs to and has no base fee:
curl "$RELVO_API_URL/tools/project" \
-H "Authorization: Bearer $RELVO_API_KEY"const api = process.env.RELVO_API_URL;
const headers = { Authorization: `Bearer ${process.env.RELVO_API_KEY}` };
const response = await fetch(`${api}/tools/project`, { headers });
console.log(await response.json());import os
import requests
api = os.environ["RELVO_API_URL"]
headers = {"Authorization": f"Bearer {os.environ['RELVO_API_KEY']}"}
response = requests.get(f"{api}/tools/project", headers=headers)
print(response.json())Discover, quote, execute, read
Find a tool by describing one capability in a few English words. Discovery returns matching tools with their prices; set summary to false to include their input schemas.
curl "$RELVO_API_URL/tools/discover" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
--data '{"query":"web search","limit":3}'const found = await fetch(`${api}/tools/discover`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ query: "web search", limit: 3 }),
}).then(response => response.json());
console.log(found.data.tools.map(tool => [tool.name, tool.price.description]));found = requests.post(f"{api}/tools/discover", headers=headers,
json={"query": "web search", "limit": 3}).json()
print([(tool["name"], tool["price"]["description"]) for tool in found["data"]["tools"]])Get a free quote for the exact tool and arguments you will run. A quote starts nothing. maximumChargeMicros is the most the call can charge.
curl "$RELVO_API_URL/tools/quote" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
--data '{"tool":"web_search","arguments":{"query":"content marketing"}}'const request = { tool: "web_search", arguments: { query: "content marketing" } };
const { data: quote } = await fetch(`${api}/tools/quote`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify(request),
}).then(response => response.json());
console.log(quote.priceVersion, quote.maximumChargeMicros);request = {"tool": "web_search", "arguments": {"query": "content marketing"}}
quote = requests.post(f"{api}/tools/quote", headers=headers, json=request).json()["data"]
print(quote["priceVersion"], quote["maximumChargeMicros"])Run the tool with the quote’s priceVersion and maximumChargeMicros. A finished execution returns HTTP 200; one still running returns HTTP 202.
IDEMPOTENCY_KEY=$(uuidgen)
curl "$RELVO_API_URL/tools/web_search" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Prefer: wait=25" \
--data '{
"tool": "web_search",
"arguments": { "query": "content marketing" },
"priceVersion": "<priceVersion from the quote>",
"maximumChargeMicros": <maximumChargeMicros from the quote>
}'import { randomUUID } from "node:crypto";
const idempotencyKey = randomUUID();
const { data: execution } = await fetch(`${api}/tools/${request.tool}`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": idempotencyKey, Prefer: "wait=25" },
body: JSON.stringify({ ...request, priceVersion: quote.priceVersion, maximumChargeMicros: quote.maximumChargeMicros }),
}).then(response => response.json());
console.log(execution.id, execution.status);import uuid
idempotency_key = str(uuid.uuid4())
execution = requests.post(
f"{api}/tools/{request['tool']}",
headers={**headers, "Idempotency-Key": idempotency_key, "Prefer": "wait=25"},
json={**request, "priceVersion": quote["priceVersion"], "maximumChargeMicros": quote["maximumChargeMicros"]},
).json()["data"]
print(execution["id"], execution["status"])Read the execution until its status is no longer queued, running or waiting. output holds the result and chargedMicros the final charge.
curl "$RELVO_API_URL/tools/executions/$EXECUTION_ID" \
-H "Authorization: Bearer $RELVO_API_KEY" \
-H "Prefer: wait=25"const active = ["queued", "running", "waiting"];
let current = execution;
while (active.includes(current.status)) {
await new Promise(resolve => setTimeout(resolve, 5000));
({ data: current } = await fetch(`${api}/tools/executions/${current.id}`, {
headers: { ...headers, Prefer: "wait=25" },
}).then(response => response.json()));
}
console.log(current.status, current.chargedMicros, current.output);import time
current = execution
while current["status"] in ("queued", "running", "waiting"):
time.sleep(5)
current = requests.get(f"{api}/tools/executions/{current['id']}",
headers={**headers, "Prefer": "wait=25"}).json()["data"]
print(current["status"], current["chargedMicros"], current["output"])Idempotency
Every submission needs an Idempotency-Key header of 8–128 characters: letters, digits, ., _, : or -. Create one per purchase and save it before sending.
- The same key with the same body returns the original execution, so retrying after a timeout never pays twice.
- The same key with a different body fails with
409platform_idempotency_key_used_for_another_request. - To find an execution whose response you lost, send
{ "idempotencyKey": "<key>" }in the body ofPOST /tools/executions/recover.data.executionis the execution, ornullif this API key created none with that key.
Wait for the result
A submission is held open for up to 10 seconds by default and returns as soon as the execution finishes. Prefer: wait=N waits up to N seconds (at most 25); Prefer: respond-async returns immediately.
An execution still in progress returns HTTP 202 with Retry-After: 5. Read GET /tools/executions/{id}, which accepts the same Prefer header, at least 5 seconds apart. When many waits are open at once, Relvo may answer immediately with the current state.
Endpoints
GET /tools/project · project:read
The team and project of the key
GET /tools/usage · usage:read
This month's requests and spend
GET /tools · tools:read
The catalog, whole or in pages of up to 50 (limit, offset)
POST /tools/discover · tools:read
Tools ranked for one capability
GET /tools/definition/{name} · tools:read
One tool's full definition
GET /tools/connections · tools:read
The project's connections
POST /tools/quote · tools:read
A free quote; starts nothing
POST /tools/{tool} · tools:execute
Reserve the quoted amount and run the tool
POST /tools/executions/recover · tools:execute
Find an execution by its idempotency key
GET /tools/executions · executions:read
Recent executions, newest first (status, tool, limit, before)
GET /tools/executions/{id} · executions:read
One execution with its output
GET /tools/executions/{id}/context · executions:read
An execution with a bounded result summary
POST /tools/executions/{id}/result · executions:read
Read part of a large result
POST /tools/executions/{id}/cancel · tools:execute
Ask an active execution to stop
GET /tools/executions/{id}/artifacts/{artifactId}/download · executions:read
A signed download URL for an artifact
POST /tools/upload · tools:execute
Upload an input file/tools/project, /tools/usage, /tools/quote, /tools/executions and /tools/connections are also served under /v1. Workflows, triggers and monitors are described in Workflows & triggers.
Build for recovery
Handle non-success responses and preserve the execution ID in your own job record. A submitted request and a finished result are different states.
After an ambiguous timeout, inspect the existing execution. Retry when you have established that another attempt is appropriate for that action.
See Running tools for result handling.