Driving Poetry Generator from your own code
Poetry Generator is a SkillSafe app, so everything the web page does is available over HTTP. There is
one contract in each direction: you post a subject, a mood and a form, and you get back one JSON
object containing the poem and the model's own account of it. This page documents it exactly as
app.js implements it.
Base URL: https://api.skillsafe.ai/v1/app-api
What the API cannot give you, and it is the important part. The scanner does not
run here. Counting the lines, deriving the rhyme scheme from the endings, checking stress against
the metre and finding the refrains all happen in the browser, in prosody.js and
forms.js. Over the API you receive the model's claims —
claimed_scheme, claimed_metre, turn_at_line — and you
should treat them the way this app does: as assertions to be checked, not as measurements. The two
files are plain JavaScript with no dependencies and no network calls; if you want the check, take
them.
The envelope
Every response is {"data": ...} on success or {"error": ...} on failure.
{"data": {"job_id": "job_...", "status": "succeeded", "charged_credits": 741, "output": {"output": "{...}"}}}
{"error": {"code": "INSUFFICIENT_CREDITS", "message": "balance below the minimum for this run"}}1. A token
Every call needs a bearer token. A guest token is free, needs no account, and is enough for
/estimate. Writing a poem is metered and needs a real account token — see
the token page.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"slug": "poetry-generator"}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/guest", headers=headers,
json={
"slug": "poetry-generator"
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/guest", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"slug": "poetry-generator"
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"slug": "poetry-generator"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/guest", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"slug": "poetry-generator"
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/guest")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"slug": "poetry-generator"}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"slug": "poetry-generator"}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/guest", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""slug"": ""poetry-generator""}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/guest", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());2. Who the token belongs to
/me returns exactly three fields: subject_type,
subject_id and credits. There is no email, no name and no id beyond the
subject id, so the test for "signed in" is subject_type === "user".
curl -s "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN"import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.get("https://api.skillsafe.ai/v1/app-api/me", headers=headers, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
}
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.GET()
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/me")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "GET",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/me", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var res = await client.GetAsync("https://api.skillsafe.ai/v1/app-api/me");
Console.WriteLine(await res.Content.ReadAsStringAsync());3. Pricing a run
/estimate is free and returns the credits that will be held. You are charged for
what the run actually uses, which is usually well under the hold.
A warning worth acting on. This endpoint performs no validation of the request
body. A bare string, a number, null and [] all return
ok: true with a well-formed estimate and a correct model binding — a malformed
body and a correct one return the same hold. So a successful estimate tells you nothing whatever
about whether your input was shaped right. Validate on your side before you send; this app ships a
mustBeObject() guard on every path that spends, for exactly this reason.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/estimate", headers=headers,
json={
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/estimate", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/estimate", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/estimate"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/estimate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/estimate", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""subject"": ""the last bus out of a town you grew up in"", ""mood"": ""elegiac"", ""form"": ""sonnet"", ""strictness"": ""strict"", ""notes"": """"}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/estimate", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());4. Writing a poem
The request body is the input object itself — there is no wrapper field.
{
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}
form is one of sonnet, villanelle, ballad,
blank, free, ghazal, limerick.
strictness is strict or loose; under loose the
model may bend the form and must then list where in departures.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/run", headers=headers,
json={
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/run")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/run", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""subject"": ""the last bus out of a town you grew up in"", ""mood"": ""elegiac"", ""form"": ""sonnet"", ""strictness"": ""strict"", ""notes"": """"}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());What comes back
output.output is a JSON string. Parse it and you get:
{
"title": "",
"reading": "",
"form": "sonnet",
"poem": {
"lines": ["", ""],
"stanza_breaks_after": [],
"claimed_scheme": "ABABCDCDEFEFGG",
"claimed_metre": "iambic pentameter",
"turn_at_line": 9,
"refrain_lines": [],
"radif": ""
},
"on_the_form": "",
"departures": [],
"craft_notes": [],
"set_aside": [{"line": "", "why": ""}]
}
poem.lines is one array element per line, with no blank strings; stanza breaks are in
stanza_breaks_after as 1-based line numbers. claimed_scheme is one letter
per line. Every one of these is the model describing its own work, which is why the page checks
them rather than printing them.
5. Streaming
/run-stream sends the same JSON as a series of deltas. The page uses it to show
progress; the parsed result is identical.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/run-stream", headers=headers,
json={
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run-stream", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"subject": "the last bus out of a town you grew up in",
"mood": "elegiac",
"form": "sonnet",
"strictness": "strict",
"notes": ""
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/run-stream"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/run-stream")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"subject": "the last bus out of a town you grew up in", "mood": "elegiac", "form": "sonnet", "strictness": "strict", "notes": ""}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/run-stream", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""subject"": ""the last bus out of a town you grew up in"", ""mood"": ""elegiac"", ""form"": ""sonnet"", ""strictness"": ""strict"", ""notes"": """"}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/run-stream", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());6. Past poems
Poems saved from the page live in a declared collection called poems, readable only by
their owner. Records nest their document under doc — read
record.doc.title, never record.title.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/collections/poems/query" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sort": {"field": "ran_at", "dir": "desc"}, "limit": 12}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/collections/poems/query", headers=headers,
json={
"sort": {
"field": "ran_at",
"dir": "desc"
},
"limit": 12
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/poems/query", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"sort": {
"field": "ran_at",
"dir": "desc"
},
"limit": 12
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"sort": {"field": "ran_at", "dir": "desc"}, "limit": 12}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/poems/query", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"sort": {
"field": "ran_at",
"dir": "desc"
},
"limit": 12
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/poems/query"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/collections/poems/query")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"sort": {"field": "ran_at", "dir": "desc"}, "limit": 12}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"sort": {"field": "ran_at", "dir": "desc"}, "limit": 12}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/collections/poems/query", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""sort"": {""field"": ""ran_at"", ""dir"": ""desc""}, ""limit"": 12}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/collections/poems/query", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());7. Finding one by meaning
/similar searches the embedded fields (title, subject,
opening) semantically.
Note the shape difference. /query resolves to
{records, next_cursor}; /similar resolves to the records
array itself. Reading .records off a similarity result yields
undefined and turns every search into a silent "no matches", which is
indistinguishable from genuinely having none.
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/collections/poems/similar" \
-H "Authorization: Bearer $SKILLSAFE_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "the one about the last bus", "limit": 10}'import os, requests
TOKEN = os.environ.get("SKILLSAFE_APP_TOKEN", "YOUR_TOKEN")
headers = {"Authorization": f"Bearer {TOKEN}"}
r = requests.post("https://api.skillsafe.ai/v1/app-api/collections/poems/similar", headers=headers,
json={
"query": "the one about the last bus",
"limit": 10
}, timeout=120)
r.raise_for_status()
print(r.json()["data"])const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/poems/similar", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"query": "the one about the last bus",
"limit": 10
})
});
const { data } = await res.json();
console.log(data);package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := os.Getenv("SKILLSAFE_APP_TOKEN")
body := []byte(`{"query": "the one about the last bus", "limit": 10}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/poems/similar", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
fmt.Println(string(b))
}import java.net.URI;
import java.net.http.*;
class Main {
public static void main(String[] args) throws Exception {
String token = System.getenv("SKILLSAFE_APP_TOKEN");
String body = """
{
"query": "the one about the last bus",
"limit": 10
}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/poems/similar"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
token = ENV["SKILLSAFE_APP_TOKEN"]
uri = URI("https://api.skillsafe.ai/v1/app-api/collections/poems/similar")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = '{"query": "the one about the last bus", "limit": 10}'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)["data"]<?php
$token = getenv("SKILLSAFE_APP_TOKEN");
$opts = ["http" => [
"method" => "POST",
"header" => "Authorization: Bearer $token\r\nContent-Type: application/json\r\n",
'content' => '{"query": "the one about the last bus", "limit": 10}',
]];
$res = file_get_contents("https://api.skillsafe.ai/v1/app-api/collections/poems/similar", false, stream_context_create($opts));
print_r(json_decode($res, true)["data"]);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = Environment.GetEnvironmentVariable("SKILLSAFE_APP_TOKEN");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var body = new StringContent(@"{""query"": ""the one about the last bus"", ""limit"": 10}", Encoding.UTF8, "application/json");
var res = await client.PostAsync("https://api.skillsafe.ai/v1/app-api/collections/poems/similar", body);
Console.WriteLine(await res.Content.ReadAsStringAsync());Errors
INSUFFICIENT_CREDITS — balance below the run minimum.
UNAUTHORIZED — missing or expired token; mint a new guest token.
RATE_LIMITED — back off and retry; /similar is limited more
tightly than /query.
Rate and size
The subject is clipped at 4,000 characters and the notes at 1,200 before the request is built, and the page tells the user how much was cut. A collection document is capped at 64 KB.