The Silicon Liquid API
Read GPU prices and what is free, price and place orders, publish requests for quotes, follow delivery, and get every change pushed to you as a signed webhook. JSON over HTTPS at https://siliconliquid.com/api/v1. Agents can use the same reads through the MCP server. Silicon Liquid takes no payments: suppliers invoice and collect.
Machine-readable: OpenAPI 3.1 · llms.txt · llms-full.txt
Quickstart
Prices, listings and availability need no key. Try one:
curl https://siliconliquid.com/api/v1/pricesEverything about your own orders, requests and deliveries needs a key. Ask the desk for one, put it in SL_API_KEY, and price an order of 8 L40s for a day:
export SL_API_KEY=sl_live_...
curl -X POST https://siliconliquid.com/api/v1/orders/estimate \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"listing":"l40","gpus":8,"hours":24}'Then list your orders with GET /orders, or read what changed with GET /events. Every endpoint, with a line to copy, is under Endpoints.
Authentication
Send the key as Authorization: Bearer sl_live_.... A key acts for one party (your company) and sees only that party's orders, requests, deals and allocations; anything else answers 404, as if it did not exist. A key is shown once, does nothing until the desk attaches a scope, and expires (90 days unless agreed otherwise). A write scope includes the read scope of the same thing.
| Scope | Lets the key |
|---|---|
orders:read | Read the party's GPU orders and estimate new ones |
orders:write | Place GPU orders (Buy now, holding the GPUs) |
rfqs:read | Read the party's requests, with their replies |
rfqs:write | Publish requests and accept quotes |
deals:read | Read the party's long-term contract requests |
deals:write | Send long-term contract requests to the desk |
delivery:read | Read the party's allocations and delivery certificates |
events:read | Read the party's events feed |
webhooks:read | List the party's webhook endpoints |
webhooks:write | Add and remove webhook endpoints |
Limits: 120 calls a minute per key, 60 a minute per address without one. Each answer says how many are left in RateLimit-Remaining.
Idempotency
Every POST takes an Idempotency-Key header, and every write requires one: a fresh random value (a UUID) per request. If the connection drops, send the same request with the same key and you get the first answer back instead of a second order. Answers are kept for 24 hours. The same key with a different request is a 422; the same key while the first is still running is a 409, so wait a moment and retry.
Errors
Every refusal has one shape. Branch on type; show message to a person, since it says what to do next; issues names each field at fault.
{ "error": { "type": "invalid_request", "message": "Check the fields named in issues.",
"issues": [{ "path": "gpus", "message": "Between 1 and 300 GPUs." }],
"requestId": "..." } }| Type | Status | When |
|---|---|---|
invalid_request | 400 | A field is wrong or missing; issues names each one. |
authentication_required | 401 | The call needs a key and none was sent. |
invalid_api_key | 401 | The key is unknown, revoked or expired. |
insufficient_scope | 403 | The key lacks the scope the call needs. |
party_blocked | 403 | The key's party may not trade here. |
not_found | 404 | No such thing for this key's party (another party's object is a 404 too). |
method_not_allowed | 405 | That method is not served on that path. |
conflict | 409 | The state does not allow it now (sold out, already taken, a limit reached). |
precondition_failed | 412 | Something must happen first (a compliance pack, say). |
idempotency_conflict | 422 | The same Idempotency-Key was sent with a different request. |
rate_limited | 429 | Too many calls; wait for Retry-After. |
internal_error | 500 | Our fault. Retry with the same Idempotency-Key. |
unavailable | 503 | A dependency is down for a moment. Retry shortly. |
Pagination
Lists take limit and starting_after and answer { data, hasMore, nextCursor }. To read the next page, pass the last nextCursor as starting_after. Orders and deals come newest first.
The events feed is the other way round: oldest first and append-only, so it doubles as a change log. Keep the last nextCursor and poll with it to see only what is new. Every event carries its link in the audit hash chain, so you can check your copy has not been altered.
curl "https://siliconliquid.com/api/v1/events?starting_after=evt_1200&limit=100" -H "Authorization: Bearer $SL_API_KEY"Webhooks
Instead of polling, register an https URL and every event your party could read at GET /events is POSTed to it, signed, within about a minute. Filter by type with eventTypes: exact (rfq.award) or a prefix ending in a full stop (order.). Up to 16 endpoints per party. The answer carries the signing secret, whsec_..., once: store it like a password.
curl -X POST https://siliconliquid.com/api/v1/webhook-endpoints \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.acme.ai/silicon-liquid","eventTypes":["order.","rfq."]}'The body is the event, exactly as GET /events shows it. Send yourself one any time with POST /webhook-endpoints/{id}/test, which answers with what your endpoint said.
POST /silicon-liquid HTTP/1.1
Content-Type: application/json
SL-Signature: t=1760866212,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
SL-Event-Id: evt_1204
SL-Event-Type: order.created_via_api
{
"id": "evt_1204",
"seq": 1204,
"type": "order.created_via_api",
"createdAt": "2026-10-19T09:30:12.418Z",
"actor": {
"type": "api_key",
"id": "sl_live_ab12cd34"
},
"object": {
"kind": "order",
"id": "k7m2q9xd"
},
"data": {
"keyId": "0b8f6a52-7c3e-4c1d-9a7e-5d2f1e3c4b6a",
"offer": "l40",
"gpus": 8,
"listingId": null
},
"hash": "9f2c4e1b7a…",
"prevHash": "41d0e8c3b2…"
}Checking the signature
- Every delivery carries SL-Signature: t=<unix seconds>,v1=<hex>. v1 is the HMAC-SHA256, keyed with the endpoint's whole secret (whsec_ included), of the timestamp, a full stop and the raw request body exactly as received: t + "." + body.
- Compute it yourself and compare in constant time; accept the request when any v1 in the header matches (during a secret rotation with an overlap there are two).
- Refuse a t more than 5 minutes from your clock, so a captured request cannot be replayed later.
- SL-Event-Id is the event's id and is the same on every attempt: use it to drop repeats. SL-Event-Type is its type.
- Answer 2xx within 10 seconds. Anything else, a redirect included, is retried: up to 8 attempts over about 24 hours. An endpoint that fails every attempt for three days is disabled.
import crypto from "node:crypto";
import express from "express";
const SECRET = process.env.SL_WEBHOOK_SECRET; // whsec_...
const app = express();
// Read the raw body: the signature covers the exact bytes sent.
app.post("/silicon-liquid", express.raw({ type: "application/json" }), (req, res) => {
const body = req.body.toString("utf8");
if (!verify(req.get("SL-Signature") ?? "", body, SECRET)) return res.sendStatus(400);
const event = JSON.parse(body);
// SL-Event-Id (= event.id) is the same on every retry: skip ones you have seen.
console.log(event.type, event.object);
res.sendStatus(200); // answer within 10 s; do slow work afterwards
});
function verify(header, body, secret, toleranceSeconds = 300) {
const parts = header.split(",").map((p) => p.trim().split("="));
const t = Number(parts.find(([k]) => k === "t")?.[1]);
const sigs = parts.filter(([k]) => k === "v1").map(([, v]) => v);
if (!t || !sigs.length) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const want = crypto.createHmac("sha256", secret).update(`${t}.${body}`).digest();
return sigs.some((s) => {
const got = Buffer.from(s, "hex");
return got.length === want.length && crypto.timingSafeEqual(got, want);
});
}
app.listen(3000);import hashlib, hmac, json, os, time
from flask import Flask, request, abort
SECRET = os.environ["SL_WEBHOOK_SECRET"] # whsec_...
app = Flask(__name__)
def verify(header: str, body: bytes, secret: str, tolerance: int = 300) -> bool:
parts = [p.strip().split("=", 1) for p in header.split(",") if "=" in p]
t = next((v for k, v in parts if k == "t"), None)
sigs = [v for k, v in parts if k == "v1"]
if not t or not t.isdigit() or not sigs or abs(time.time() - int(t)) > tolerance:
return False
want = hmac.new(secret.encode(), t.encode() + b"." + body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(want, s) for s in sigs)
@app.post("/silicon-liquid")
def hook():
body = request.get_data() # the raw bytes, before any JSON parsing
if not verify(request.headers.get("SL-Signature", ""), body, SECRET):
abort(400)
event = json.loads(body)
# SL-Event-Type says the type; SL-Event-Id repeats on retries.
print(event["type"], event["object"])
return "", 200Retries, rotation and switching off
A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 5 hours, 8 hours and 8 hours: eight attempts over about 24 hours, then it is given up (the event stays in GET /events). Events can arrive out of order and more than once, so order by seq and drop repeats by SL-Event-Id.
Rotate the secret with POST /webhook-endpoints/{id} and {"rotateSecret": true, "expirePreviousInHours": 24}: for those hours both secrets sign and the header carries two v1 values, so you can deploy the new secret without dropping anything. {"disabled": true} pauses an endpoint and DELETE removes it. An endpoint whose every attempt has failed for three days is disabled for you, with the reason in disabledReason; set {"disabled": false} once it is fixed.
URLs must be https on a public address: private, loopback and link-local addresses are refused when you register and again on every connection, and redirects are not followed.
MCP server
AI agents can read the venue through the Model Context Protocol at https://siliconliquid.com/api/mcp (Streamable HTTP, stateless). Use the same API key as a bearer token; the public tools also work without one. Every tool only reads: nothing is held, ordered or paid through MCP.
claude mcp add --transport http silicon-liquid https://siliconliquid.com/api/mcp \
--header "Authorization: Bearer $SL_API_KEY"{
"mcpServers": {
"silicon-liquid": {
"type": "http",
"url": "https://siliconliquid.com/api/mcp",
"headers": {
"Authorization": "Bearer sl_live_..."
}
}
}
}curl -s https://siliconliquid.com/api/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $SL_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_prices","arguments":{"chip":"h100"}}}'| Tool | What it does | Key |
|---|---|---|
get_prices | The public GPU price reference: per chip, today's on-demand median across providers, spot and reserved prices, and the day and week changes. With a chip, each provider's line and the daily history. Sources whose terms allow republishing only. | none |
get_availability | For every listing on sale, how many GPUs (pools) or servers (windows) are free in each hour of the next eight weeks, with the price per GPU-hour. Filter by chip, kind or the least that must be free. | none |
recommend | Turns a plain-language need into one recommended package and at most two others: what you get in plain words, how to buy it, an all-in monthly estimate with its source and a real start date. Map the user's words onto the fields: job (train, fine-tune, serve...), size (by model size in billions of parameters, by GPU count, by monthly budget, or unsure), start (now, a date, or unsure) and term. | none |
list_listings | Open listings with their chip, price, capacity, delivery class, evidence level and next eight weeks. Never the supplier or its floor price. | none |
get_listing | One listing by its uuid, or by a catalog offer id ("l40", "l40g"). An ended listing still answers, with onSale false. | none |
estimate_order | What an order of some GPUs on one listing costs per hour, day and month, and for a window of whole hours (UTC), and whether the GPUs are free now. Holds nothing and places nothing. | orders:read |
my_orders | The orders this key's party placed through the API: their status in plain words, price and GPUs. With a ref, one order and its history. | orders:read |
my_allocations | The capacity this key's party has been allocated (as buyer) or is delivering (as supplier): state, window, nodes, price and delivery certificates. With an id, one allocation with its history and access grants. | delivery:read |
get_certificate | A signed acceptance-test result for one of the party's allocations: every stage and its metrics, the hash chain and the Ed25519 signature. | delivery:read |
Endpoints
All under https://siliconliquid.com/api/v1. Window times are whole hours, UTC. The full request and response schemas are in the OpenAPI document.
Catalog
/skusno key neededList the SKUs the venue sells
Chip, GPUs per server, memory and fabric: two servers of one SKU are interchangeable.
curl "https://siliconliquid.com/api/v1/skus"/availabilityno key neededWhat is free on each listing, week by week
For every listing on sale, what is free in every hour of each of the next eight weeks (GPUs for a pool, whole servers for a window). Week 0 starts today at 00:00 UTC.
curl "https://siliconliquid.com/api/v1/availability?chip=l40"/listingsno key neededList what is on sale
Open listings with their price, capacity, delivery class, evidence level and next eight weeks. Never the supplier or its floor.
curl "https://siliconliquid.com/api/v1/listings?kind=pool"/listings/{id}no key neededOne listing
By its uuid, or by a catalog offer id (l40, l40g). Ended listings still answer, with onSale false.
curl "https://siliconliquid.com/api/v1/listings/l40"/recommendno key neededThree questions in, one package out
What you want to do, how big, and when and for how long. Answers with one recommended package and at most two others: what you get in plain words, the lane that buys it, an all-in monthly estimate with its source and a real start date.
curl -X POST "https://siliconliquid.com/api/v1/recommend" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"job":"train","size":{"by":"model","paramsB":8},"start":{"by":"now"},"term":"weeks"}'Prices
/pricesno key neededThe public price reference, every chip
Today's market per chip from the sources whose terms allow republishing: the on-demand median across providers, spot, reserved (never blended) and the day and week changes.
curl "https://siliconliquid.com/api/v1/prices"/prices/{chip}no key neededThe public price reference for one chip
Each provider's line and the daily history, public sources only.
curl "https://siliconliquid.com/api/v1/prices/h100"Orders
/orders/estimateorders:readPrice an order before placing it
What an order on a listing costs per hour, day and month, and for a window of whole hours, and whether the GPUs are free now. Nothing is held.
curl -X POST "https://siliconliquid.com/api/v1/orders/estimate" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"listing":"l40","gpus":8,"startAt":"2026-11-02T14:00:00Z","endAt":"2026-11-09T14:00:00Z"}'/ordersorders:writePlace an order (Buy now)
Buy now on a listed pool: the GPUs are held for 12 hours in the same transaction as the order, or the order is refused with what is free. Nothing is charged: once the desk confirms, the supplier invoices the buyer directly. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/orders" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"listing":"6f1c2a9e-4b7d-4e2a-9c1f-2d3e4f5a6b7c","offer":"l40","gpus":8,"preset":"qwen3-30b","email":"ops@acme.ai","company":"Acme AI","terms":true}'/ordersorders:readThe party's orders
Orders placed with this party's keys, newest first.
curl "https://siliconliquid.com/api/v1/orders" \
-H "Authorization: Bearer $SL_API_KEY"/orders/{ref}orders:readOne order
Its status in the buyer's words, its hold and its history. Another party's order is a 404.
curl "https://siliconliquid.com/api/v1/orders/abcd2345" \
-H "Authorization: Bearer $SL_API_KEY"Requests
/rfqsrfqs:writePublish a request for quotes
The same request the /rfq form publishes: suppliers quote on its public link, and the replies are this party's alone. The owner key in the answer opens the replies page in a browser; the API needs only the API key. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/rfqs" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"spec":{"kind":"compute","side":"BUY","gpu":"H100","gpusPerNode":8,"units":8,"gpus":64,"country":"US","termMonths":3,"priceMode":"R"}}'/rfqs/{ref}rfqs:readOne request, with its replies for the owner
Anyone with the scope reads the public request; the party that published it also gets the replies, ranked by the store, with suppliers named only once awarded.
curl "https://siliconliquid.com/api/v1/rfqs/abcd2345" \
-H "Authorization: Bearer $SL_API_KEY"/rfqs/{ref}/awardsrfqs:writeAccept a quote
Accept one firm quote; call again for more until the request is covered. Refused unless the quote can still be taken and both compliance packs exist. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/rfqs/abcd2345/awards" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"quoteId":42}'Deals
/dealsdeals:writeAsk the desk for a long-term contract
A Data-centre direct request (6 to 60 months from partner data centres). A named person replies the same business day; the room shows anonymous options within two business days. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/deals" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"job":"train","gpu":"B200","nodes":32,"termMonths":24,"region":"north_america","email":"infra@acme.ai","company":"Acme AI"}'/dealsdeals:readThe party's long-term contract requests
curl "https://siliconliquid.com/api/v1/deals" \
-H "Authorization: Bearer $SL_API_KEY"/deals/{ref}deals:readOne deal room, as the buyer sees it
Its step, timeline, the anonymous options and the messages and documents for the buyer. Another party's deal is a 404.
curl "https://siliconliquid.com/api/v1/deals/abcd2345" \
-H "Authorization: Bearer $SL_API_KEY"/deals/{ref}/interestdeals:writeSay you are interested in one anonymous option
Starts the NDA step for that option: your NDA and the data centre's, each to sign by email code; then the desk passes names. Saying it twice changes nothing. There is no payment endpoint: the data centre invoices you directly. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/deals/abcd2345/interest" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"optionId":"6f1c2a9e-4b7d-4e2a-9c1f-2d3e4f5a6b7c"}'Delivery
/allocations/{id}delivery:readOne allocation
Its state, history, certificates and access grants, for the buyer or the supplier behind it.
curl "https://siliconliquid.com/api/v1/allocations/17" \
-H "Authorization: Bearer $SL_API_KEY"/certificates/{id}delivery:readOne delivery certificate
The signed acceptance-test result: every stage and its metrics, the hash chain and the Ed25519 signature.
curl "https://siliconliquid.com/api/v1/certificates/5" \
-H "Authorization: Bearer $SL_API_KEY"Events
/eventsevents:readThe party's events, oldest first
Append-only, read forward with starting_after: every audit-chain row about this party or its orders, requests and deals, with who acted. Poll with the last nextCursor to see only what is new.
curl "https://siliconliquid.com/api/v1/events?starting_after=evt_120&limit=50" \
-H "Authorization: Bearer $SL_API_KEY"Webhooks
/webhook-endpointswebhooks:writeAdd a webhook endpoint
Every event this party could read at GET /events is also POSTed here, signed with the secret in the answer (shown once). https only, on a public address. At most 16 per party. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"url":"https://hooks.acme.ai/silicon-liquid","description":"Ops alerts","eventTypes":["order.","rfq."]}'/webhook-endpointswebhooks:readThe party's webhook endpoints
Each with its health: the last success and failure, failures in a row, and deliveries still to go. Never the secret.
curl "https://siliconliquid.com/api/v1/webhook-endpoints" \
-H "Authorization: Bearer $SL_API_KEY"/webhook-endpoints/{id}webhooks:writeChange, disable or re-enable an endpoint, or rotate its secret
Fields left out stay as they are. rotateSecret answers with the new secret, once; expirePreviousInHours keeps the old one signing alongside it for up to 72 hours so receivers can switch without dropping events. Requires an Idempotency-Key.
curl -X POST "https://siliconliquid.com/api/v1/webhook-endpoints/0b8f6a52-7c3e-4c1d-9a7e-5d2f1e3c4b6a" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"rotateSecret":true,"expirePreviousInHours":24}'/webhook-endpoints/{id}webhooks:writeDelete an endpoint
It stops at once; deliveries still waiting to be sent to it are dropped. Its events stay in GET /events.
curl -X DELETE "https://siliconliquid.com/api/v1/webhook-endpoints/0b8f6a52-7c3e-4c1d-9a7e-5d2f1e3c4b6a" \
-H "Authorization: Bearer $SL_API_KEY"/webhook-endpoints/{id}/testwebhooks:writeSend a test event now
A signed "webhook_endpoint.test" event, sent at once and answered with what the endpoint said. Never retried, and never counted toward the endpoint's failures.
curl -X POST "https://siliconliquid.com/api/v1/webhook-endpoints/0b8f6a52-7c3e-4c1d-9a7e-5d2f1e3c4b6a/test" \
-H "Authorization: Bearer $SL_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"Questions, or a key: arpit@siliconliquid.com. New here? Learn how the market works.