Build with SynthBoard
Quickstart
Each of your users gets their own office: the same experts, their own file and record, isolated per user and billed to your organization. A plain question gets a plain answer — every structured field is optional and empty unless the matter calls for it.
1. Get a test key#
In the developer console, create an organization and a test key (sbk_test_…). Test keys return sample answers from fixtures and never call a model or spend. Keep the key in your server's environment as SYNTHBOARD_KEY — never in a browser or an app bundle.
Install an SDK, or use plain HTTP:
npm install @synthboard/sdk
# or
pip install synthboard2. Create an office for one of your users#
curl
curl -X POST https://www.synthboard.ai/api/v1/offices \
-H "Authorization: Bearer $SYNTHBOARD_KEY" \
-H "Content-Type: application/json" \
-d '{"external_user_id":"user_123","lang":"en"}'TypeScript
import { SynthBoard } from '@synthboard/sdk';
const sb = new SynthBoard({ apiKey: process.env.SYNTHBOARD_KEY! });
const office = await sb.createOffice({ external_user_id: 'user_123', lang: 'en' });
// Show office.notice to your user before their first ask.Python
import os
from synthboard import SynthBoard
sb = SynthBoard(os.environ["SYNTHBOARD_KEY"])
office = sb.create_office({"external_user_id": "user_123", "lang": "en"})
# Show office["notice"] to your user before their first ask.The office is keyed by your own external_user_id: creating it again returns the same office. Show its notice to your user once, before their first ask.
3. Ask it something#
curl
curl -X POST https://www.synthboard.ai/api/v1/offices/$OFFICE_ID/ask \
-H "Authorization: Bearer $SYNTHBOARD_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"kind":"question","text":"Should I take the offer?"}'TypeScript
let turn = await sb.ask(office.id, { kind: 'question', text: 'Should I take the offer?' });Python
turn = sb.ask(office["id"], {"kind": "question", "text": "Should I take the offer?"})Every ask carries an Idempotency-Key (the SDKs send one for you): a retry returns the same turn and never buys a second answer. Within about 25 seconds you get the answer; past that, status: "working" and a turn_id.
4. Read the answer#
curl
curl https://www.synthboard.ai/api/v1/offices/$OFFICE_ID/turns/$TURN_ID?wait=25 \
-H "Authorization: Bearer $SYNTHBOARD_KEY"TypeScript
if (turn.status === 'working') turn = await sb.waitForTurn(office.id, turn.turn_id);
console.log(turn.reply);Python
if turn["status"] == "working":
turn = sb.wait_for_turn(office["id"], turn["turn_id"])
print(turn["reply"])Read what came back#
The JSON and the same answer rendered, side by side — the sandbox's own response. The first answer in an office carries notice; an ordinary question carries no call.
What your app sends, and what comes back
POST /v1/offices/eb692f9c-a278-4445-9548-acd8af9a1dde/ask
Idempotency-Key: panes-demo-1
{
"text": "My landlord wants to raise the rent 9% mid-lease. Can he? (expert)"
}
{
"schema_version": "0.8.0",
"status": "ok",
"turn_id": "1742fbd1-f65b-4f02-a92e-50b48dce45bd",
"lang": "en",
"link": "https://www.synthboard.ai/developers/sandbox",
"speaker": "expert",
"handoff": {
"expert": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "The Career Coach",
"role": "Career Coach",
"face": null,
"hue": "#5b5bd6",
"runnable": true
},
"brought": {
"their_words": true,
"fact_count": 0,
"reads": 0
},
"swap_options": []
},
"expert_turns": [
{
"turn_id": "1742fbd1-f65b-4f02-a92e-50b48dce45bd",
"session_id": "d50b092b-5f42-473f-873e-63aba1188f75",
"matter_id": "",
"lane_synth_id": "22222222-2222-4222-8222-222222222222",
"expert": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "The Career Coach",
"role": "Career Coach",
"face": null,
"hue": "#5b5bd6",
"runnable": true
},
"via": "route",
"state": "delivered",
"weight": "full",
"body_md": "**The strongest route: lecturing at a teaching college.** A sandbox expert answer.",
"doc": {
"segments": [
{
"t": "md",
"md": "**The strongest route: lecturing at a teaching college.** A sandbox expert answer."
}
],
"frame": {
"call": null,
"ask": null,
"meta": null
},
"issues": [],
"budget": {
"components": 0,
"interactive": 0,
"calcs": 0,
"by_kind": {},
"longest_prose_words": 12,
"limits": null
},
"complete": true
},
"call": {
"line": "Apply to the teaching college first.",
"would_change_it": "If another opening appears before December.",
"when_we_know": "2026-11-15"
},
"split_signal": null,
"used_from_you": [],
"sources": [],
"computed": [],
"second_opinion": null,
"checked": null,
"follow_offer": null,
"cost": {
"used": null
},
"timing": {
"strip_ms": null,
"first_content_ms": null,
"first_word_ms": null,
"done_ms": null
},
"made_with": {
"model": "sandbox",
"lab": "sandbox",
"recipe": "sandbox",
"effort": "none"
},
"answer": {
"shape": null,
"reply_md": "**The strongest route: lecturing at a teaching college.** A sandbox expert answer.\n\n**My call: Apply to the teaching college first.**\nWhat would change it: If another opening appears before December.\nWhen we'll know: Sunday 15 November 2026",
"reply_text": "The strongest route: lecturing at a teaching college. A sandbox expert answer.\n\nMy call: Apply to the teaching college first.\nWhat would change it: If another opening appears before December.\nWhen we'll know: Sunday 15 November 2026",
"blocks": [
{
"type": "markdown",
"md": "**The strongest route: lecturing at a teaching college.** A sandbox expert answer."
}
],
"drafts": [],
"call": {
"line": "Apply to the teaching college first.",
"would_change_it": "If another opening appears before December.",
"when_we_know": "2026-11-15"
},
"question": null
}
}
],
"matter": null,
"record": {},
"speed": {
"class": "answer",
"expected_by": "2026-10-25T09:00:56.000Z",
"basis": {
"source": "static",
"stat": "p50",
"seconds": 56,
"n": null,
"measured_at": null
}
},
"follow_offer": null,
"saved_facts": [],
"moves": [],
"cards": [],
"follow_ups": [],
"files": [],
"cost": {
"used": 0,
"balance_after": 0
},
"steps": [],
"provenance": {
"ai_generated": true,
"models": [
{
"model": "sandbox",
"lab": "sandbox"
}
]
},
"opinion": {
"who": [
{
"practice": "22222222-2222-4222-8222-222222222222",
"lead": {
"id": "22222222-2222-4222-8222-222222222222",
"name": "The Career Coach",
"role": "Career Coach",
"face": null,
"hue": "#5b5bd6",
"runnable": true
}
}
],
"call": {
"line": "Apply to the teaching college first.",
"would_change_it": "If another opening appears before December.",
"when_we_know": "2026-11-15"
},
"grade": null,
"scope_limits": [],
"evidence": [],
"computed": [],
"unchecked": [],
"method": {
"practice_id": "22222222-2222-4222-8222-222222222222",
"method_version": null,
"contract_version": "0.8.0",
"model": "sandbox",
"lab": "sandbox",
"recipe": "sandbox"
}
},
"legacy_status": "done",
"reply": "**The strongest route: lecturing at a teaching college.** A sandbox expert answer.",
"agent_cards": [],
"disclosure": "Experts and answers here are AI.",
"notice": "Answers here come from SynthBoard's AI experts. What you send here is shared with SynthBoard to answer you."
}See how it renders ↓The strongest route: lecturing at a teaching college. A sandbox expert answer.
The experts are AI — named roles, not people.
5. Go live#
Add credits, set a monthly cap (a live key can only be made once a cap is set), then make a live key (sbk_live_…). Live calls bill your organization's balance at actual cost after they run; nothing new starts at or below zero, and work already running always finishes. The machine-readable price list is GET /api/v1/prices.
Every endpoint, field and error: the platform reference. Agents and MCP clients: MCP tools.
The experts are AI — named roles, not people. How this works