Developers · API v1

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/prices

Everything 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.

ScopeLets the key
orders:readRead the party's GPU orders and estimate new ones
orders:writePlace GPU orders (Buy now, holding the GPUs)
rfqs:readRead the party's requests, with their replies
rfqs:writePublish requests and accept quotes
deals:readRead the party's long-term contract requests
deals:writeSend long-term contract requests to the desk
delivery:readRead the party's allocations and delivery certificates
events:readRead the party's events feed
webhooks:readList the party's webhook endpoints
webhooks:writeAdd 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": "..." } }
TypeStatusWhen
invalid_request400A field is wrong or missing; issues names each one.
authentication_required401The call needs a key and none was sent.
invalid_api_key401The key is unknown, revoked or expired.
insufficient_scope403The key lacks the scope the call needs.
party_blocked403The key's party may not trade here.
not_found404No such thing for this key's party (another party's object is a 404 too).
method_not_allowed405That method is not served on that path.
conflict409The state does not allow it now (sold out, already taken, a limit reached).
precondition_failed412Something must happen first (a compliance pack, say).
idempotency_conflict422The same Idempotency-Key was sent with a different request.
rate_limited429Too many calls; wait for Retry-After.
internal_error500Our fault. Retry with the same Idempotency-Key.
unavailable503A 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.

What arrives
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.
Node (Express)
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);
Python (Flask)
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 "", 200

Retries, 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 Code
claude mcp add --transport http silicon-liquid https://siliconliquid.com/api/mcp \
  --header "Authorization: Bearer $SL_API_KEY"
Any client that takes a JSON config
{
  "mcpServers": {
    "silicon-liquid": {
      "type": "http",
      "url": "https://siliconliquid.com/api/mcp",
      "headers": {
        "Authorization": "Bearer sl_live_..."
      }
    }
  }
}
Or by hand
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"}}}'
ToolWhat it doesKey
get_pricesThe 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_availabilityFor 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
recommendTurns 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_listingsOpen listings with their chip, price, capacity, delivery class, evidence level and next eight weeks. Never the supplier or its floor price.none
get_listingOne listing by its uuid, or by a catalog offer id ("l40", "l40g"). An ended listing still answers, with onSale false.none
estimate_orderWhat 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_ordersThe 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_allocationsThe 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_certificateA 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

GET/skusno key needed

List 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"
GET/availabilityno key needed

What 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"
GET/listingsno key needed

List 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"
GET/listings/{id}no key needed

One 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"
POST/recommendno key needed

Three 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

GET/pricesno key needed

The 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"
GET/prices/{chip}no key needed

The 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

POST/orders/estimateorders:read

Price 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"}'
POST/ordersorders:write

Place 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}'
GET/ordersorders:read

The 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"
GET/orders/{ref}orders:read

One 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

POST/rfqsrfqs:write

Publish 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"}}'
GET/rfqs/{ref}rfqs:read

One 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"
POST/rfqs/{ref}/awardsrfqs:write

Accept 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

POST/dealsdeals:write

Ask 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"}'
GET/dealsdeals:read

The party's long-term contract requests

curl "https://siliconliquid.com/api/v1/deals" \
  -H "Authorization: Bearer $SL_API_KEY"
GET/deals/{ref}deals:read

One 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"
POST/deals/{ref}/interestdeals:write

Say 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

GET/allocations/{id}delivery:read

One 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"
GET/certificates/{id}delivery:read

One 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

GET/eventsevents:read

The 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

POST/webhook-endpointswebhooks:write

Add 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."]}'
GET/webhook-endpointswebhooks:read

The 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"
POST/webhook-endpoints/{id}webhooks:write

Change, 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}'
DELETE/webhook-endpoints/{id}webhooks:write

Delete 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"
POST/webhook-endpoints/{id}/testwebhooks:write

Send 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.