A REST API for AI medical intake. Authenticate with an API key from your dashboard (header X-API-Key). All requests and responses are JSON.
Every intake is driven by the same configurable form template. Choose the integration style that fits your stack — you can mix and match:
Use a secret key (isk_...) from your dashboard. The pattern: define a template once, start a session, loop POST /sessions/:id/turn until completed, then fetch the structured intake.
# 1. Define a template (once)
curl -X POST https://intakescribe-api-production.up.railway.app/templates \
-H "X-API-Key: $INTAKESCRIBE_KEY" -H "Content-Type: application/json" \
-d '{ "name": "New Patient Intake",
"fields": [
{ "id": "full_name", "prompt": "the patient's full name", "type": "text" },
{ "id": "dob", "prompt": "date of birth", "type": "date" },
{ "id": "reason", "prompt": "reason for visit", "type": "text" } ] }'
# 2. Start a session
curl -X POST https://intakescribe-api-production.up.railway.app/sessions \
-H "X-API-Key: $INTAKESCRIBE_KEY" -H "Content-Type: application/json" \
-d '{ "template_id": "tmpl_..." }'
# 3. Send a turn (repeat until completed)
curl -X POST https://intakescribe-api-production.up.railway.app/sessions/SESSION_ID/turn \
-H "X-API-Key: $INTAKESCRIBE_KEY" -H "Content-Type: application/json" \
-d '{ "message": "Jane Doe" }'
# 4. Fetch the completed intake
curl https://intakescribe-api-production.up.railway.app/intakes/INTAKE_ID -H "X-API-Key: $INTAKESCRIBE_KEY"Use the official TypeScript client in the browser or on the server. The API surface is identical — only the key type changes. The SDK is a thin wrapper over the REST API, so if you'd rather not add a dependency you can call the endpoints directly with fetch (see the REST API quickstart) — that path always works, no install required.
# (npm package "@intakescribe/sdk" coming soon — not on npm yet)
# Build the SDK from the repo and link it locally:
git clone https://github.com/Abhisg5/intakeScribe.git
cd intakeScribe/sdk
npm install && npm run build
npm link # then `npm link @intakescribe/sdk` in your project
# Prefer zero dependencies? Skip the SDK and call the REST API with fetch
# (see the REST API quickstart above).Publishable keys (pk_...) are safe to ship in client-side bundles. They are origin-restricted (configure allowed origins in the dashboard) and never expose completed intake data — completed records are only accessible with your secret key or via your completion webhook.
import { createClient } from "@intakescribe/sdk";
const client = createClient({
publishableKey: "pk_live_...", // browser-safe; origin-restricted
baseUrl: "https://intakescribe-api-production.up.railway.app",
});
// Start a session — returns the first question
const session = await client.startSession("tmpl_...");
console.log(session.message);
// Send answers turn by turn until the intake is complete
let result = await client.sendTurn(session.session_id, "Jane Doe");
while (!result.completed) {
const userInput = await yourUI.prompt(result.message);
result = await client.sendTurn(session.session_id, userInput);
}
// Done. On the publishable (browser) path the completed record is NOT returned
// here for PHI safety — result.intake_id is undefined. Receive the finished
// intake on your server via the completion webhook, or fetch it with your
// secret key. (The server/secret-key path does return result.intake_id.)
// Or stream the bot reply token by token:
await client.streamTurn(session.session_id, "Jane Doe", (delta) => {
appendToChat(delta);
});Secret keys (isk_...) have access to the full data plane including completed intakes. Keep them server-side only.
import { createClient } from "@intakescribe/sdk";
const client = createClient({
secretKey: process.env.INTAKESCRIBE_KEY, // isk_... — server-side only
baseUrl: "https://intakescribe-api-production.up.railway.app",
});
// Same API surface as the browser client
const session = await client.startSession("tmpl_...");
const result = await client.sendTurn(session.session_id, "Jane Doe");
// On the secret-key path, result.intake_id is populated once completed —
// fetch the structured record with GET /intakes/:id.Add one <script> tag to get our prebuilt chat + voice UI. By default it mounts as a floating bubble in the bottom-right corner; use data-target to embed it inline. Create a publishable key and configure allowed origins in the dashboard Integrate page.
<!-- Drop this anywhere in your page -->
<script
src="https://intakescribe.abhix-ai.com/widget/v1.js"
data-pk="pk_live_..."
data-template="tmpl_...">
</script>
<!-- Inline (embedded in a container instead of a floating bubble): -->
<div id="intake-widget"></div>
<script
src="https://intakescribe.abhix-ai.com/widget/v1.js"
data-pk="pk_live_..."
data-template="tmpl_..."
data-target="#intake-widget">
</script>
<!-- Enable voice mode by default: -->
<script
src="https://intakescribe.abhix-ai.com/widget/v1.js"
data-pk="pk_live_..."
data-template="tmpl_..."
data-voice="1">
</script>No integration required. Go to the dashboard Integrate page, create a hosted link for your template, and share the URL with patients:
https://intakescribe.abhix-ai.com/i/<your-slug>Patients open the link, complete the intake (chat or voice), and the results arrive on your server via your configured completion webhook — or you can poll GET /intakes/:id with your secret key. Hosted pages support voice out of the box; patients can switch between chat and voice at any time.
Patients can speak instead of type at no extra cost. The browser handles speech-to-text and text-to-speech entirely on-device using the Web Speech API — you only pay for the same LLM tokens as a text intake.
Voice is built into both the widget and hosted page. Enable it by default with data-voice="1" on the widget script tag (patients can always switch to chat). Patients without a supported browser fall back to the text chat automatically. Best support: Chrome and Edge desktop.
<!-- Widget with voice enabled by default -->
<script
src="https://intakescribe.abhix-ai.com/widget/v1.js"
data-pk="pk_live_..."
data-template="tmpl_..."
data-voice="1">
</script>The intakescribe-mcp package wraps the REST API as Model Context Protocol tools so an AI agent (Claude Desktop, Claude Code, or any MCP client) can automate intakeScribe — create forms, run intakes, and read results — without writing any glue code.
# (PyPI release coming soon.) Install directly from the repo:
pip install "intakescribe-mcp @ git+https://github.com/Abhisg5/intakeScribe.git#subdirectory=mcp"
# Or, from a local checkout of the mcp/ directory:
# pip install -e .
# Either way you get the `intakescribe-mcp` console command (used in the config below).{
"mcpServers": {
"intakescribe": {
"command": "intakescribe-mcp",
"env": {
"INTAKESCRIBE_API_KEY": "isk_live_...",
"INTAKESCRIBE_API_URL": "https://intakescribe-api-production.up.railway.app"
}
}
}
}| Tool | Description |
|---|---|
| list_templates() | List all intake form templates for the account |
| create_template(name, fields, description?) | Create a new intake form; returns the new template with its id |
| get_template(template_id) | Inspect a template's full field schema |
| start_intake(template_id) | Start a session; returns session_id + the opening question |
| send_patient_message(session_id, message) | Send a patient answer; returns the next question, completed flag, and intake_id when done |
| get_session(session_id) | Get a session's current status and full transcript |
| get_intake(intake_id) | Retrieve the completed, structured intake (contains PHI) |
| get_usage() | Check account usage totals |
Use /turn/stream to stream the agent's reply token-by-token. Concatenate the deltas, then read the final done event.
POST https://intakescribe-api-production.up.railway.app/sessions/SESSION_ID/turn/stream
→ Content-Type: text/event-stream
event: message
data: {"delta": "What is "}
event: message
data: {"delta": "your date of birth?"}
event: done
data: {"session_id": "...", "status": "in_progress", "completed": false, "intake_id": null}Set completion_webhook_url on a template. On completion we POST the signed payload. Verify the X-IntakeScribe-Signature header (HMAC-SHA256 of the raw body with your key's webhook secret).
import crypto from "node:crypto";
function verify(rawBody, signature, secret) {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
// Express handler (use the RAW body, not the parsed JSON):
app.post("/hook", express.raw({ type: "*/*" }), (req, res) => {
const sig = req.header("X-IntakeScribe-Signature");
if (!verify(req.body, sig, process.env.WEBHOOK_SECRET)) return res.status(401).end();
const event = JSON.parse(req.body.toString());
// event.payload is the completed intake
res.json({ ok: true });
});import hmac, hashlib
def verify(raw_body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
# FastAPI:
@app.post("/hook")
async def hook(request: Request):
raw = await request.body()
sig = request.headers.get("X-IntakeScribe-Signature", "")
if not verify(raw, sig, WEBHOOK_SECRET):
raise HTTPException(401)
event = json.loads(raw) # event["payload"] = completed intake
return {"ok": True}| Code | Meaning |
|---|---|
| 200 / 201 | Success |
| 401 | Missing or invalid API key |
| 404 | Resource not found (or not yours) |
| 409 | Session already completed |
| 429 | Monthly token quota exceeded |