Drive PAA Answer Writer 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.
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. |
The input: one lane, task: "answer"
Every input carries task: "answer" (the app has one lane). Send pasted questions in questions, or leave it out and set count to get suggested questions. Fields are scalars; facts travels as a JSON string.
| Field | Type | Meaning |
|---|---|---|
task | string, required | Always "answer". |
keyword | string, required | The topic or search keyword, up to 160 characters. |
questions | string | The questions to answer, one per line, up to 10. The web app sends them after cleanup. |
count | number | 3–10: how many questions to suggest when questions is empty. Default 6. |
audience | string | Who reads the answers. |
context | string | Facts about your business the answers may use (prices, location, policies). The web app sends at most 3,000 characters, cut at a line break. |
tone | string | "plain", "expert" or "friendly". |
spelling | string | "en-US" or "en-GB". |
facts | string (JSON) | The free in-browser cleanup: {"questions":[{"question","intent","format"}]}. Optional. |
retry_note | string | Sent only on the one automatic reformat retry. |
The output contract
The output (output.output) is one JSON object: task, status (ok · off_topic · refused), message, keyword, search_intent,
items[], related[] (follow-up questions, suggestions) and notes.
Each item: question, origin (pasted or suggested), intent (definition · how_to · comparison · cost · yes_no · list · reason · time · other), format (paragraph · list · steps),
snippet (40–60 words, plain text, answer first), list_items[], expanded (paragraphs separated by a blank line) and verify[] (claims to check before publishing).
The web app counts each snippet's words and builds the FAQPage JSON-LD in the browser. If you call the API directly, do your own count: the model aims for 40–60 words but is not guaranteed to hit it.
1. Get a token
A guest token is enough for /me and /estimate. Writing answers 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 answer, not the price. Pasted questions:
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}'
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
body = {
"task": "answer",
"keyword": "air source heat pump",
"questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?",
"tone": "plain",
"spelling": "en-GB",
"facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"
}
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": "answer",
"keyword": "air source heat pump",
"questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?",
"tone": "plain",
"spelling": "en-GB",
"facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"
};
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": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}`)
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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}";
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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}"
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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}";
$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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}", Encoding.UTF8, "application/json");
var res = await http.PostAsync($"{baseUrl}/estimate", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());
Keyword only, with suggested questions:
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"task": "answer", "keyword": "sourdough starter", "count": 6, "tone": "plain", "spelling": "en-US"}'
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
body = {
"task": "answer",
"keyword": "sourdough starter",
"count": 6,
"tone": "plain",
"spelling": "en-US"
}
r = requests.post(f"{BASE}/estimate", headers={**H}, json=body, timeout=120).json()
print(r["data"]["hold_credits"])
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": "answer",
"keyword": "sourdough starter",
"count": 6,
"tone": "plain",
"spelling": "en-US"
};
const r = await (await fetch(`${BASE}/estimate`, { method: "POST", headers: { ...H }, body: JSON.stringify(body) })).json();
console.log(r.data.hold_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() {
payload := []byte(`{"task": "answer", "keyword": "sourdough starter", "count": 6, "tone": "plain", "spelling": "en-US"}`)
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\": \"answer\", \"keyword\": \"sourdough starter\", \"count\": 6, \"tone\": \"plain\", \"spelling\": \"en-US\"}";
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\": \"answer\", \"keyword\": \"sourdough starter\", \"count\": 6, \"tone\": \"plain\", \"spelling\": \"en-US\"}"
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\": \"answer\", \"keyword\": \"sourdough starter\", \"count\": 6, \"tone\": \"plain\", \"spelling\": \"en-US\"}";
$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\": \"answer\", \"keyword\": \"sourdough starter\", \"count\": 6, \"tone\": \"plain\", \"spelling\": \"en-US\"}", 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.
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: paa-answer-writer:answer:1kq8zt3-2f4:a1" \
-d '{"task": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}'
import requests
TOKEN = "YOUR_TOKEN"
BASE = "https://api.skillsafe.ai/v1/app-api"
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
body = {
"task": "answer",
"keyword": "air source heat pump",
"questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?",
"tone": "plain",
"spelling": "en-GB",
"facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"
}
r = requests.post(f"{BASE}/run", headers={**H, "Idempotency-Key": "paa-answer-writer:answer:1kq8zt3-2f4: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": "answer",
"keyword": "air source heat pump",
"questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?",
"tone": "plain",
"spelling": "en-GB",
"facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"
};
const r = await (await fetch(`${BASE}/run`, { method: "POST", headers: { ...H, "Idempotency-Key": "paa-answer-writer:answer:1kq8zt3-2f4: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": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}`)
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", "paa-answer-writer:answer:1kq8zt3-2f4: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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}";
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", "paa-answer-writer:answer:1kq8zt3-2f4: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"] = "paa-answer-writer:answer:1kq8zt3-2f4:a1"
req.body = "{\"task\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}"
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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}";
$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: paa-answer-writer:answer:1kq8zt3-2f4: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\": \"answer\", \"keyword\": \"air source heat pump\", \"questions\": \"Do air source heat pumps work in cold weather?\\nHow much does an air source heat pump cost to run?\", \"tone\": \"plain\", \"spelling\": \"en-GB\", \"facts\": \"{\\\"questions\\\": [{\\\"question\\\": \\\"Do air source heat pumps work in cold weather?\\\", \\\"intent\\\": \\\"yes_no\\\", \\\"format\\\": \\\"paragraph\\\"}, {\\\"question\\\": \\\"How much does an air source heat pump cost to run?\\\", \\\"intent\\\": \\\"cost\\\", \\\"format\\\": \\\"paragraph\\\"}]}\"}", Encoding.UTF8, "application/json");
http.DefaultRequestHeaders.Add("Idempotency-Key", "paa-answer-writer:answer:1kq8zt3-2f4: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 answer set 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)
answers = json.loads(job["output"]["output"])
for it in answers["items"]:
print(it["question"], "->", it["snippet"])
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 answers = JSON.parse(job.output.output);
for (const it of answers.items) console.log(it.question, "->", it.snippet);
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 answer set 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"])["items"]
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)["items"]);
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 answer set 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: paa-answer-writer:answer:1kq8zt3-2f4:a1" \
-d '{"task": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}'
# 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 answer set>"}, "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": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}
with requests.post(f"{BASE}/run-stream", json=body, stream=True, timeout=300,
headers={**H, "Accept": "text/event-stream", "Idempotency-Key": "paa-answer-writer:answer:1kq8zt3-2f4: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:])
answers = json.loads(done["output"]["output"])
print(len(answers["items"]), 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": "paa-answer-writer:answer:1kq8zt3-2f4:a1" },
body: JSON.stringify({"task": "answer", "keyword": "air source heat pump", "questions": "Do air source heat pumps work in cold weather?\nHow much does an air source heat pump cost to run?", "tone": "plain", "spelling": "en-GB", "facts": "{\"questions\": [{\"question\": \"Do air source heat pumps work in cold weather?\", \"intent\": \"yes_no\", \"format\": \"paragraph\"}, {\"question\": \"How much does an air source heat pump cost to run?\", \"intent\": \"cost\", \"format\": \"paragraph\"}]}"}) });
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 answers = JSON.parse(done.output.output);
console.log(answers.items.length, 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.