Data API
Everything here is about the words you keep. The generator itself needs no API at all — it runs in the browser, and the reproducible way to share a batch is the seeded link the page puts in your address bar, not a request to a server.
There is no generation endpoint, and that is not an omission
Random Word Generator has no AI model. Coinages are produced entirely in the visitor's tab from the
phonotactic grammars shipped with the page, so there is no server-side generation to call and
nothing to meter. The app holds the platform's Completely free label; while
that is held, the platform's billable endpoints return 403 for this app.
Want a batch reproduced somewhere else? Use the seed. Every batch is deterministic from its seed, and the seed travels in the URL fragment — open the same link and you get the same words. That is a stronger guarantee than an API call, because it holds offline too.
Base URL, envelope and errors
https://api.skillsafe.ai/v1/app-api
Every response is one of two shapes. Success carries data; failure carries
error with a stable code. Nothing else is at the top level, so a
client that reads data and falls back to error handles every call
on this page.
{"data": { ... }}
{"error": {"code": "UNAUTHORIZED", "message": "token missing or expired"}}
| HTTP | code | what it means here |
|---|---|---|
| 400 | VALIDATION_ERROR | A field is the wrong type. The usual cause is saved_at: it is a declared timestamp and wants ISO-8601 with a Z. |
| 401 | UNAUTHORIZED | No token, or it has expired. Mint a guest or sign in again. |
| 403 | FORBIDDEN | The record belongs to another subject, or you called a billable endpoint this free app does not have. |
| 404 | NOT_FOUND | No such record or key. A first read of the settings key returns this and it is expected. |
| 429 | RATE_LIMITED | Search by meaning is 30 a minute per IP. Back off; do not retry in a tight loop. |
One environment note that costs people an hour: api.skillsafe.ai answers
Python's default urllib user agent with a Cloudflare 1010. It
reads like an auth or endpoint failure and is neither. Send a real
User-Agent header, as the Python samples below do.
1 Get a token
Every call carries a bearer token. The easiest way to get one is the session page: it shows the token this browser already holds and gives you a shell export line to paste. If you have no browser in the loop, mint a guest identity — a guest owns its own kept words and needs no account.
POST https://api.skillsafe.ai/v1/app-api/guest
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/guest" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/guest"
body = {}
req = urllib.request.Request(URL, data=json.dumps(body).encode(), method="POST")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
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({}),
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
body := []byte(`{}`)
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")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{}"""))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
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 = {}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)['data']
<?php
$token = 'YOUR_TOKEN'; // from /tokens.html
$body = '{}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/guest',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"),
"https://api.skillsafe.ai/v1/app-api/guest");
req.Headers.Add("Authorization", "Bearer " + Token);
req.Content = new StringContent(@"{}",
System.Text.Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"token":"aut_...","subject_type":"guest","subject_id":"gst_..."}}
2 See who the token is
Three fields come back and only three: subject_type, subject_id and credits. There is no email and no display name, so the signed-in test is subject_type === "user" and nothing else. credits is always reported and is always irrelevant here — Random Word Generator has no billable endpoint.
GET https://api.skillsafe.ai/v1/app-api/me
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN"
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/me"
req = urllib.request.Request(URL, data=None, method="GET")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
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")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
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 = 'YOUR_TOKEN'; // from /tokens.html
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/me',
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("GET"),
"https://api.skillsafe.ai/v1/app-api/me");
req.Headers.Add("Authorization", "Bearer " + Token);
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"subject_type":"user","subject_id":"usr_...","credits":0}}
3 Keep a coinage
One record per kept word. saved_at is a declared timestamp field and accepts only ISO-8601 with a trailing Z — epoch milliseconds, epoch seconds and a naive ISO string are all rejected with Field type mismatch.
POST https://api.skillsafe.ai/v1/app-api/collections/coinages/records
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/collections/coinages/records" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"word": "kavinor", "flavour": "elvish", "pronunciation": "KA-vi-nor", "note": "shortlist for the mapping product", "syllables": 3, "seed": "silmaril", "saved_at": "2026-08-26T12:00:00Z"}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/collections/coinages/records"
body = {
"word": "kavinor",
"flavour": "elvish",
"pronunciation": "KA-vi-nor",
"note": "shortlist for the mapping product",
"syllables": 3,
"seed": "silmaril",
"saved_at": "2026-08-26T12:00:00Z"
}
req = urllib.request.Request(URL, data=json.dumps(body).encode(), method="POST")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/coinages/records", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"word": "kavinor",
"flavour": "elvish",
"pronunciation": "KA-vi-nor",
"note": "shortlist for the mapping product",
"syllables": 3,
"seed": "silmaril",
"saved_at": "2026-08-26T12:00:00Z"
}),
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
body := []byte(`{"word": "kavinor", "flavour": "elvish", "pronunciation": "KA-vi-nor", "note": "shortlist for the mapping product", "syllables": 3, "seed": "silmaril", "saved_at": "2026-08-26T12:00:00Z"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/coinages/records", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/coinages/records"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"word": "kavinor",
"flavour": "elvish",
"pronunciation": "KA-vi-nor",
"note": "shortlist for the mapping product",
"syllables": 3,
"seed": "silmaril",
"saved_at": "2026-08-26T12:00:00Z"
}"""))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/collections/coinages/records')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{TOKEN}"
req['Content-Type'] = 'application/json'
req.body = {"word": "kavinor", "flavour": "elvish", "pronunciation": "KA-vi-nor", "note": "shortlist for the mapping product", "syllables": 3, "seed": "silmaril", "saved_at": "2026-08-26T12:00:00Z"}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)['data']
<?php
$token = 'YOUR_TOKEN'; // from /tokens.html
$body = '{"word": "kavinor", "flavour": "elvish", "pronunciation": "KA-vi-nor", "note": "shortlist for the mapping product", "syllables": 3, "seed": "silmaril", "saved_at": "2026-08-26T12:00:00Z"}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/collections/coinages/records',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"),
"https://api.skillsafe.ai/v1/app-api/collections/coinages/records");
req.Headers.Add("Authorization", "Bearer " + Token);
req.Content = new StringContent(@"{""word"": ""kavinor"", ""flavour"": ""elvish"", ""pronunciation"": ""KA-vi-nor"", ""note"": ""shortlist for the mapping product"", ""syllables"": 3, ""seed"": ""silmaril"", ""saved_at"": ""2026-08-26T12:00:00Z""}",
System.Text.Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"record_id":"rec_..."}}
4 List what you have kept
A query returns {records, next_cursor}, and each record is {record_id, doc:{…}} — the fields live under doc, never flat on the record. Page with cursor when next_cursor is present.
POST https://api.skillsafe.ai/v1/app-api/collections/coinages/query
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/collections/coinages/query" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sort": {"field": "saved_at", "dir": "desc"}, "limit": 30}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/collections/coinages/query"
body = {
"sort": {
"field": "saved_at",
"dir": "desc"
},
"limit": 30
}
req = urllib.request.Request(URL, data=json.dumps(body).encode(), method="POST")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/coinages/query", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"sort": {
"field": "saved_at",
"dir": "desc"
},
"limit": 30
}),
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
body := []byte(`{"sort": {"field": "saved_at", "dir": "desc"}, "limit": 30}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/coinages/query", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/coinages/query"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"sort": {
"field": "saved_at",
"dir": "desc"
},
"limit": 30
}"""))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/collections/coinages/query')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{TOKEN}"
req['Content-Type'] = 'application/json'
req.body = {"sort": {"field": "saved_at", "dir": "desc"}, "limit": 30}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)['data']
<?php
$token = 'YOUR_TOKEN'; // from /tokens.html
$body = '{"sort": {"field": "saved_at", "dir": "desc"}, "limit": 30}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/collections/coinages/query',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"),
"https://api.skillsafe.ai/v1/app-api/collections/coinages/query");
req.Headers.Add("Authorization", "Bearer " + Token);
req.Content = new StringContent(@"{""sort"": {""field"": ""saved_at"", ""dir"": ""desc""}, ""limit"": 30}",
System.Text.Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"records":[{"record_id":"rec_...","doc":{"word":"kavinor","flavour":"elvish"}}],"next_cursor":null}}
5 Search them by meaning
Vector search over word, flavour and note, so "the one for the mapping product" finds it without remembering the word. This one resolves to the records ARRAY itself, not to {records} like a query does — destructuring {records} off it yields undefined and renders as "nothing matched". Rate limited to 30 a minute per IP.
POST https://api.skillsafe.ai/v1/app-api/collections/coinages/similar
curl -s -X POST "https://api.skillsafe.ai/v1/app-api/collections/coinages/similar" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"text": "the one for the mapping product", "limit": 8}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/collections/coinages/similar"
body = {
"text": "the one for the mapping product",
"limit": 8
}
req = urllib.request.Request(URL, data=json.dumps(body).encode(), method="POST")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/coinages/similar", {
method: "POST",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "the one for the mapping product",
"limit": 8
}),
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
body := []byte(`{"text": "the one for the mapping product", "limit": 8}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/coinages/similar", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/coinages/similar"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("""
{
"text": "the one for the mapping product",
"limit": 8
}"""))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/collections/coinages/similar')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{TOKEN}"
req['Content-Type'] = 'application/json'
req.body = {"text": "the one for the mapping product", "limit": 8}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)['data']
<?php
$token = 'YOUR_TOKEN'; // from /tokens.html
$body = '{"text": "the one for the mapping product", "limit": 8}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/collections/coinages/similar',
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"),
"https://api.skillsafe.ai/v1/app-api/collections/coinages/similar");
req.Headers.Add("Authorization", "Bearer " + Token);
req.Content = new StringContent(@"{""text"": ""the one for the mapping product"", ""limit"": 8}",
System.Text.Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":[{"record_id":"rec_...","score":0.81,"doc":{"word":"kavinor"}}]}
6 Delete one
Deletion is by record id and is immediate. There is no undo and no soft delete, so read before you write if that matters to you.
DELETE https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME
curl -s -X DELETE "https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN"
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME"
req = urllib.request.Request(URL, data=None, method="DELETE")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME", {
method: "DELETE",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
req, _ := http.NewRequest("DELETE", "https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME')
req = Net::HTTP::Delete.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 = 'YOUR_TOKEN'; // from /tokens.html
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME',
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("DELETE"),
"https://api.skillsafe.ai/v1/app-api/collections/coinages/records/rec_REPLACE_ME");
req.Headers.Add("Authorization", "Bearer " + Token);
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"deleted":true}}
7 Read and write your settings
The generator's control positions live in a per-subject key-value slot rather than in a collection, because staleness there is harmless. Reads are cached for about ninety seconds per subject and key — which is exactly why the kept words are collection rows instead.
PUT https://api.skillsafe.ai/v1/app-api/data/prefs
curl -s -X PUT "https://api.skillsafe.ai/v1/app-api/data/prefs" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": {"flavour": "elvish", "minSyllables": 3, "maxSyllables": 4, "exotic": 40, "count": 16, "checkWords": true, "checkNames": true, "checkBrands": true}}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/data/prefs"
body = {
"value": {
"flavour": "elvish",
"minSyllables": 3,
"maxSyllables": 4,
"exotic": 40,
"count": 16,
"checkWords": true,
"checkNames": true,
"checkBrands": true
}
}
req = urllib.request.Request(URL, data=json.dumps(body).encode(), method="PUT")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/data/prefs", {
method: "PUT",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"value": {
"flavour": "elvish",
"minSyllables": 3,
"maxSyllables": 4,
"exotic": 40,
"count": 16,
"checkWords": true,
"checkNames": true,
"checkBrands": true
}
}),
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
body := []byte(`{"value": {"flavour": "elvish", "minSyllables": 3, "maxSyllables": 4, "exotic": 40, "count": 16, "checkWords": true, "checkNames": true, "checkBrands": true}}`)
req, _ := http.NewRequest("PUT", "https://api.skillsafe.ai/v1/app-api/data/prefs", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/data/prefs"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("PUT", HttpRequest.BodyPublishers.ofString("""
{
"value": {
"flavour": "elvish",
"minSyllables": 3,
"maxSyllables": 4,
"exotic": 40,
"count": 16,
"checkWords": true,
"checkNames": true,
"checkBrands": true
}
}"""))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/data/prefs')
req = Net::HTTP::Put.new(uri)
req['Authorization'] = "Bearer #{TOKEN}"
req['Content-Type'] = 'application/json'
req.body = {"value": {"flavour": "elvish", "minSyllables": 3, "maxSyllables": 4, "exotic": 40, "count": 16, "checkWords": true, "checkNames": true, "checkBrands": true}}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts JSON.parse(res.body)['data']
<?php
$token = 'YOUR_TOKEN'; // from /tokens.html
$body = '{"value": {"flavour": "elvish", "minSyllables": 3, "maxSyllables": 4, "exotic": 40, "count": 16, "checkWords": true, "checkNames": true, "checkBrands": true}}';
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/data/prefs',
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("PUT"),
"https://api.skillsafe.ai/v1/app-api/data/prefs");
req.Headers.Add("Authorization", "Bearer " + Token);
req.Content = new StringContent(@"{""value"": {""flavour"": ""elvish"", ""minSyllables"": 3, ""maxSyllables"": 4, ""exotic"": 40, ""count"": 16, ""checkWords"": true, ""checkNames"": true, ""checkBrands"": true}}",
System.Text.Encoding.UTF8, "application/json");
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"ok":true}}
8 Check your usage
How much of the per-subject storage allowance the kept words occupy. This is the only resource Random Word Generator consumes on anyone's behalf, and the app shows it on the front page for the same reason it is documented here.
GET https://api.skillsafe.ai/v1/app-api/storage
curl -s -X GET "https://api.skillsafe.ai/v1/app-api/storage" \
-H "Authorization: Bearer $RANDOM_WORD_GENERATOR_TOKEN"
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html
URL = "https://api.skillsafe.ai/v1/app-api/storage"
req = urllib.request.Request(URL, data=None, method="GET")
req.add_header("Authorization", "Bearer " + TOKEN)
req.add_header("Content-Type", "application/json")
# Cloudflare answers urllib's default user agent with a 1010, which reads
# like an auth failure and is not one. Send a real one.
req.add_header("User-Agent", "random-word-generator-client/1.0")
with urllib.request.urlopen(req) as r:
print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN"; // from /tokens.html
const res = await fetch("https://api.skillsafe.ai/v1/app-api/storage", {
method: "GET",
headers: {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(data);
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
const token = "YOUR_TOKEN" // from /tokens.html
func main() {
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/storage", nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var out map[string]any
raw, _ := io.ReadAll(res.Body)
json.Unmarshal(raw, &out)
fmt.Println(out["data"])
}
import java.net.URI;
import java.net.http.*;
public class RandomWordGenerator {
static final String TOKEN = "YOUR_TOKEN"; // from /tokens.html
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.skillsafe.ai/v1/app-api/storage"))
.header("Authorization", "Bearer " + TOKEN)
.header("Content-Type", "application/json")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_TOKEN' # from /tokens.html
uri = URI('https://api.skillsafe.ai/v1/app-api/storage')
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 = 'YOUR_TOKEN'; // from /tokens.html
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.skillsafe.ai/v1/app-api/storage',
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
],
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($out['data']);
using System;
using System.Net.Http;
using System.Threading.Tasks;
class RandomWordGenerator {
const string Token = "YOUR_TOKEN"; // from /tokens.html
static async Task Main() {
var http = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("GET"),
"https://api.skillsafe.ai/v1/app-api/storage");
req.Headers.Add("Authorization", "Bearer " + Token);
var res = await http.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
}
}
Response
{"data":{"used_bytes":2048,"limit_bytes":10485760,"collections":1}}
The record shape
One collection, coinages. word, flavour and
note are the embedded fields, which is what step 5 searches over.
| field | type | what it holds |
|---|---|---|
word | string | The coinage itself. Embedded. |
flavour | string | One of neobrand, latinate, germanic, slavic, nihongo, oceanic, semitic, steppe, elvish. Embedded. |
pronunciation | string | Syllable hint with the stressed syllable upper-cased, e.g. KA-vi-nor. |
note | string | Whatever you wrote next to it. Embedded, so it is what search by meaning actually keys off. |
syllables | number | Syllable count of the finished word. |
seed | string | The seed of the batch it came from, so a kept word can be traced back to its siblings. |
saved_at | timestamp | ISO-8601 with a Z. Nothing else is accepted. |
Reproducing a batch without an API
The generator is deterministic. A batch is fully described by its flavour, syllable range, exotic dial, both constraints, the check flags and the seed — and all of that is encoded in the URL fragment the page maintains as you work:
https://random-word-generator.skillsafe.ai/#f=elvish&s=3-4&x=40&n=16&c=wnb&seed=silmaril
| key | meaning |
|---|---|
f | flavour id |
s | syllable range, min-max |
x | familiar-to-exotic dial, 0–100 |
n | how many words |
sw / ew | starts-with / ends-with, letters only |
c | checks: w dictionary, n names, b trademarks |
seed | the seed — the same one always gives the same batch |
The offensive-term check has no letter in c because it cannot be switched off.