Drive Doctor Visit Prep from your own code
Everything the web app does goes through one public surface. Base URL:
https://api.skillsafe.ai/v1/app-api
Every request carries Authorization: Bearer <token> and Content-Type: application/json.
Every response is a JSON envelope: {"ok":true,"data":{…}} on success and
{"ok":false,"error":{"code":"…","message":"…"}} on failure. Read error.code, not only the HTTP status.
The app organizes a person's own notes for a clinician. It does not diagnose, triage or recommend treatment, and neither does its output when called from code.
Errors
| Code | Meaning | What to do |
|---|---|---|
UNAUTHORIZED | Missing, expired or wrong-app token. | Mint a guest token or sign in again. |
INSUFFICIENT_CREDITS | Balance below min_credits. | Top up. /estimate is free and tells you the hold. |
VALIDATION_ERROR | The input object failed validation. | Check error.details. The body is the input object itself — never wrap it in {"input": …}. |
RATE_LIMITED | Too many requests. | Back off and retry. |
JOB_FAILED | The run started and did not complete. | Retry with the same Idempotency-Key. |
Input
One flat object of strings. task and concern are required; everything else may be empty. The web app sends at most these lengths, cutting long fields on line boundaries from the middle with a marker.
| Field | Type | Meaning |
|---|---|---|
task | string, required | Always "prep". |
concern | string, required | The main reason for the visit (600). |
for_whom | string | "self", "child" or "other". Sets the point of view of the questions. |
age, sex | string | As the person gives them. |
visit_type | string | primary, specialist, urgent, telehealth, follow_up or empty. |
story | string | What happened and when, ideally one line per event starting with the time (5,000). |
meds | string | Medicines and supplements, one per line (1,500). |
allergies, conditions, tried, goals | string | Free text (400, 1,200, 1,200, 600). |
prescan | string (JSON) | The web app's free warning-sign check: {"flags":[{"id","sign","level"}],"meds_parsed":[…],"clipped":[…]}. Every flag is required to appear in the reply's red_flags. Send {"flags":[]} if you have no check of your own. |
retry_note | string | Sent only on the one automatic reformat retry. |
Output
The output (output.output) is one JSON object: task, status (ok · urgent · need_more · not_health), message, title,
summary (chief_concern, onset, history, pertinent[], meds[] of {name, dose, how_often, reason, note}, allergies[], conditions[], tried[], goals),
timeline[] of {when, event, kind} with kind in onset · change · treatment · test · visit · other,
questions[] of {q, why, priority} with priority top or also,
they_may_ask[] of {q, answer}, bring[],
red_flags[] of {sign, action, why} with action emergency · urgent · call, and missing[].
status: "urgent" means the notes describe a warning sign happening now: show message first. The model may wrap the object in a code fence; strip it before parsing. The web app also reads the reply for diagnosis-like sentences and flags them; if you call the API directly, do the same.
1. Get a token
A guest token is enough for /me and /estimate. Running is metered and needs a personal token from signing in. The tokens page shows yours and copies it. Replace YOUR_TOKEN below with it.
2. Check the session and the balance
/me returns subject_type, subject_id and credits. Signed in means subject_type === "user".
curl -s "https://api.skillsafe.ai/v1/app-api/me" \ -H "Authorization: Bearer YOUR_TOKEN"
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
r = requests.get(f"{BASE}/me", headers=H, timeout=30).json()
print(r["data"]["subject_type"], r["data"]["credits"])
const TOKEN = "YOUR_TOKEN";
const BASE = "https://api.skillsafe.ai/v1/app-api";
const H = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
const r = await (await fetch(`${BASE}/me`, { headers: H })).json();
console.log(r.data.subject_type, r.data.credits);
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
func main() {
req, _ := http.NewRequest("GET", base+"/me", nil)
req.Header.Set("Authorization", "Bearer "+token)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var out map[string]any
json.NewDecoder(res.Body).Decode(&out)
fmt.Println(out["data"])
_ = bytes.NewReader
}
var token = "YOUR_TOKEN";
var base = "https://api.skillsafe.ai/v1/app-api";
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/me"))
.header("Authorization", "Bearer " + token).GET().build();
System.out.println(http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
uri = URI("#{BASE}/me")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$base = "https://api.skillsafe.ai/v1/app-api";
$ch = curl_init("$base/me");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
print_r(json_decode(curl_exec($ch), true)["data"]);
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var baseUrl = "https://api.skillsafe.ai/v1/app-api";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Console.WriteLine(await http.GetStringAsync($"{baseUrl}/me"));
3. Price it before you run it
/estimate is free and starts no job. hold_credits is a reservation priced against the longest possible sheet, not the price. There is one lane, prep:
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}'
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
body = {
"task": "prep",
"for_whom": "self",
"age": "46",
"sex": "Female",
"visit_type": "primary",
"concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.",
"story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night",
"meds": "lisinopril 10 mg once daily for blood pressure",
"allergies": "penicillin (rash)",
"conditions": "high blood pressure",
"tried": "honey, cough syrup for 3 nights",
"goals": "Find out why it is not going away and sleep through the night.",
"prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"
}
r = requests.post(f"{BASE}/estimate", headers={**H}, json=body, timeout=120).json()
print(r["data"]["hold_credits"], r["data"]["model_alias"])
const TOKEN = "YOUR_TOKEN";
const BASE = "https://api.skillsafe.ai/v1/app-api";
const H = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
const body = {
"task": "prep",
"for_whom": "self",
"age": "46",
"sex": "Female",
"visit_type": "primary",
"concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.",
"story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night",
"meds": "lisinopril 10 mg once daily for blood pressure",
"allergies": "penicillin (rash)",
"conditions": "high blood pressure",
"tried": "honey, cough syrup for 3 nights",
"goals": "Find out why it is not going away and sleep through the night.",
"prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"
};
const r = await (await fetch(`${BASE}/estimate`, { method: "POST", headers: { ...H }, body: JSON.stringify(body) })).json();
console.log(r.data.hold_credits, r.data.model_alias);
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
func main() {
payload := []byte(`{"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}`)
req, _ := http.NewRequest("POST", base+"/estimate", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var out map[string]any
json.NewDecoder(res.Body).Decode(&out)
fmt.Println(out["data"])
}
var token = "YOUR_TOKEN";
var base = "https://api.skillsafe.ai/v1/app-api";
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}";
var req = java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/estimate"))
.header("Authorization", "Bearer " + token).header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
uri = URI("#{BASE}/estimate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req.body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$base = "https://api.skillsafe.ai/v1/app-api";
$body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}";
$ch = curl_init("$base/estimate");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", "Content-Type: application/json"],
]);
print_r(json_decode(curl_exec($ch), true)["data"]);
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var baseUrl = "https://api.skillsafe.ai/v1/app-api";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent("{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}", Encoding.UTF8, "application/json");
var res = await http.PostAsync($"{baseUrl}/estimate", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());
4. Run and poll
/run returns a job. Always send an Idempotency-Key — the same key is the same run, so a retry cannot bill twice. The web app derives it from the lane and a hash of the input, plus an attempt number for its one automatic reformat retry.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: doctor-visit-prep:prep:k3x9q1-2f0:a1" \
-d '{"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}'
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
body = {
"task": "prep",
"for_whom": "self",
"age": "46",
"sex": "Female",
"visit_type": "primary",
"concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.",
"story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night",
"meds": "lisinopril 10 mg once daily for blood pressure",
"allergies": "penicillin (rash)",
"conditions": "high blood pressure",
"tried": "honey, cough syrup for 3 nights",
"goals": "Find out why it is not going away and sleep through the night.",
"prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"
}
r = requests.post(f"{BASE}/run", headers={**H, "Idempotency-Key": "doctor-visit-prep:prep:k3x9q1-2f0:a1"}, json=body, timeout=120).json()
job_id = r["data"]["job_id"]
const TOKEN = "YOUR_TOKEN";
const BASE = "https://api.skillsafe.ai/v1/app-api";
const H = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
const body = {
"task": "prep",
"for_whom": "self",
"age": "46",
"sex": "Female",
"visit_type": "primary",
"concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.",
"story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night",
"meds": "lisinopril 10 mg once daily for blood pressure",
"allergies": "penicillin (rash)",
"conditions": "high blood pressure",
"tried": "honey, cough syrup for 3 nights",
"goals": "Find out why it is not going away and sleep through the night.",
"prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"
};
const r = await (await fetch(`${BASE}/run`, { method: "POST", headers: { ...H, "Idempotency-Key": "doctor-visit-prep:prep:k3x9q1-2f0:a1" }, body: JSON.stringify(body) })).json();
const jobId = r.data.job_id;
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
func main() {
payload := []byte(`{"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}`)
req, _ := http.NewRequest("POST", base+"/run", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "doctor-visit-prep:prep:k3x9q1-2f0:a1")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
var out map[string]any
json.NewDecoder(res.Body).Decode(&out)
fmt.Println(out["data"])
}
var token = "YOUR_TOKEN";
var base = "https://api.skillsafe.ai/v1/app-api";
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}";
var req = java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/run"))
.header("Authorization", "Bearer " + token).header("Content-Type", "application/json")
.header("Idempotency-Key", "doctor-visit-prep:prep:k3x9q1-2f0:a1")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString()).body());
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
uri = URI("#{BASE}/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
req["Content-Type"] = "application/json"
req["Idempotency-Key"] = "doctor-visit-prep:prep:k3x9q1-2f0:a1"
req.body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]
<?php
$token = "YOUR_TOKEN";
$base = "https://api.skillsafe.ai/v1/app-api";
$body = "{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}";
$ch = curl_init("$base/run");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => ["Authorization: Bearer $token", "Content-Type: application/json", "Idempotency-Key: doctor-visit-prep:prep:k3x9q1-2f0:a1"],
]);
print_r(json_decode(curl_exec($ch), true)["data"]);
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var baseUrl = "https://api.skillsafe.ai/v1/app-api";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent("{\"task\": \"prep\", \"for_whom\": \"self\", \"age\": \"46\", \"sex\": \"Female\", \"visit_type\": \"primary\", \"concern\": \"A dry cough that has lasted more than three weeks after a cold, worse at night.\", \"story\": \"3 weeks ago: cold with sore throat\\n10 days ago: cold gone but dry cough stayed\\nlast week: cough waking me 2-3 times a night\", \"meds\": \"lisinopril 10 mg once daily for blood pressure\", \"allergies\": \"penicillin (rash)\", \"conditions\": \"high blood pressure\", \"tried\": \"honey, cough syrup for 3 nights\", \"goals\": \"Find out why it is not going away and sleep through the night.\", \"prescan\": \"{\\\"flags\\\": [], \\\"meds_parsed\\\": [\\\"lisinopril\\\"], \\\"clipped\\\": []}\"}", Encoding.UTF8, "application/json");
http.DefaultRequestHeaders.Add("Idempotency-Key", "doctor-visit-prep:prep:k3x9q1-2f0:a1");
var res = await http.PostAsync($"{baseUrl}/run", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());
curl -s "https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID" \ -H "Authorization: Bearer YOUR_TOKEN" # repeat until data.status is "succeeded" or "failed"; # the visit sheet is the JSON string at data.output.output
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
import json, time
job_id = "JOB_ID"
while True:
job = requests.get(f"{BASE}/jobs/{job_id}", headers=H, timeout=30).json()["data"]
if job["status"] in ("succeeded", "failed"):
break
time.sleep(1)
sheet = json.loads(job["output"]["output"])
print(sheet["status"], [q["q"] for q in sheet["questions"]])
const TOKEN = "YOUR_TOKEN";
const BASE = "https://api.skillsafe.ai/v1/app-api";
const H = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
let job;
for (;;) {
job = (await (await fetch(`${BASE}/jobs/JOB_ID`, { headers: H })).json()).data;
if (job.status === "succeeded" || job.status === "failed") break;
await new Promise((r) => setTimeout(r, 1000));
}
const sheet = JSON.parse(job.output.output);
console.log(sheet.status, sheet.questions.map((q) => q.q));
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
// GET base+"/jobs/JOB_ID" every second until data.status is
// "succeeded" or "failed", then json.Unmarshal data.output.output.
func main() { fmt.Println("poll", base+"/jobs/JOB_ID"); _ = bytes.NewReader; _ = json.Marshal; _ = http.Get }
var token = "YOUR_TOKEN"; var base = "https://api.skillsafe.ai/v1/app-api"; var http = java.net.http.HttpClient.newHttpClient(); // GET base + "/jobs/JOB_ID" every second until data.status is // "succeeded" or "failed"; the visit sheet is the JSON string at data.output.output.
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
loop do
uri = URI("#{BASE}/jobs/JOB_ID")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{TOKEN}"
job = JSON.parse(Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }.body)["data"]
if %w[succeeded failed].include?(job["status"])
puts JSON.parse(job["output"]["output"])["questions"]
break
end
sleep 1
end
<?php
$token = "YOUR_TOKEN";
$base = "https://api.skillsafe.ai/v1/app-api";
do {
$ch = curl_init("$base/jobs/JOB_ID");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"]]);
$job = json_decode(curl_exec($ch), true)["data"];
if (!in_array($job["status"], ["succeeded", "failed"])) sleep(1);
} while (!in_array($job["status"], ["succeeded", "failed"]));
print_r(json_decode($job["output"]["output"], true)["questions"]);
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var baseUrl = "https://api.skillsafe.ai/v1/app-api";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// GET {baseUrl}/jobs/JOB_ID every second until data.status is
// "succeeded" or "failed"; the visit sheet is the JSON string at data.output.output.
5. Stream it
/run-stream answers with server-sent events and ends with a done frame carrying the whole output, charged_credits and truncated. If truncated is true the balance ran low mid-answer: parse what arrived and top up for the rest.
curl -sN -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "Idempotency-Key: doctor-visit-prep:prep:k3x9q1-2f0:a1" \
-d '{"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}'
# event: job data: {"job_id": ...}
# event: delta data: {"text": "..."} (may be absent: a browser often gets only
# event: tick heartbeats and the whole answer in the final frame)
# event: done data: {"output": {"output": "<the JSON visit sheet>"}, "charged_credits": ..., "truncated": false}
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
import json
body = {"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}
with requests.post(f"{BASE}/run-stream", json=body, stream=True, timeout=300,
headers={**H, "Accept": "text/event-stream", "Idempotency-Key": "doctor-visit-prep:prep:k3x9q1-2f0:a1"}) as r:
event, done = None, None
for line in r.iter_lines(decode_unicode=True):
if line.startswith("event:"): event = line[6:].strip()
elif line.startswith("data:") and event == "done": done = json.loads(line[5:])
sheet = json.loads(done["output"]["output"])
print(sheet["title"], done.get("charged_credits"), done.get("truncated"))
const TOKEN = "YOUR_TOKEN";
const BASE = "https://api.skillsafe.ai/v1/app-api";
const H = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
const res = await fetch(`${BASE}/run-stream`, { method: "POST",
headers: { ...H, Accept: "text/event-stream", "Idempotency-Key": "doctor-visit-prep:prep:k3x9q1-2f0:a1" },
body: JSON.stringify({"task": "prep", "for_whom": "self", "age": "46", "sex": "Female", "visit_type": "primary", "concern": "A dry cough that has lasted more than three weeks after a cold, worse at night.", "story": "3 weeks ago: cold with sore throat\n10 days ago: cold gone but dry cough stayed\nlast week: cough waking me 2-3 times a night", "meds": "lisinopril 10 mg once daily for blood pressure", "allergies": "penicillin (rash)", "conditions": "high blood pressure", "tried": "honey, cough syrup for 3 nights", "goals": "Find out why it is not going away and sleep through the night.", "prescan": "{\"flags\": [], \"meds_parsed\": [\"lisinopril\"], \"clipped\": []}"}) });
const text = await res.text(); // or read res.body incrementally
let done = null;
for (const frame of text.split("\n\n")) {
const ev = /^event: *(.*)$/m.exec(frame), data = /^data: *(.*)$/m.exec(frame);
if (ev && ev[1] === "done" && data) done = JSON.parse(data[1]);
}
const sheet = JSON.parse(done.output.output);
console.log(sheet.title, done.truncated);
package main
import ("bytes"; "encoding/json"; "fmt"; "net/http")
const base = "https://api.skillsafe.ai/v1/app-api"
const token = "YOUR_TOKEN"
// POST base+"/run-stream" with Accept: text/event-stream, read lines with
// bufio.Scanner, and json.Unmarshal the data line of the "done" event.
func main() { fmt.Println("see the cURL tab for the frame format"); _ = bytes.NewReader; _ = json.Marshal; _ = http.Get }
var token = "YOUR_TOKEN"; var base = "https://api.skillsafe.ai/v1/app-api"; var http = java.net.http.HttpClient.newHttpClient(); // POST base + "/run-stream" with Accept: text/event-stream, read the body // line by line (BodyHandlers.ofLines()), and parse the data line of the "done" event.
require "net/http"
require "json"
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
# POST to "#{BASE}/run-stream" with Accept: text/event-stream; read the body,
# split frames on blank lines, and JSON.parse the data line of the "done" event.
<?php $token = "YOUR_TOKEN"; $base = "https://api.skillsafe.ai/v1/app-api"; // POST to "$base/run-stream" with Accept: text/event-stream; split the body on // blank lines and json_decode the data line of the "done" event.
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var baseUrl = "https://api.skillsafe.ai/v1/app-api";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
// POST {baseUrl}/run-stream with Accept: text/event-stream; read the response
// stream line by line and parse the data line of the "done" event.