← Random Word Generator / Data API

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"}}
HTTPcodewhat it means here
400VALIDATION_ERRORA field is the wrong type. The usual cause is saved_at: it is a declared timestamp and wants ISO-8601 with a Z.
401UNAUTHORIZEDNo token, or it has expired. Mint a guest or sign in again.
403FORBIDDENThe record belongs to another subject, or you called a billable endpoint this free app does not have.
404NOT_FOUNDNo such record or key. A first read of the settings key returns this and it is expected.
429RATE_LIMITEDSearch 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.

fieldtypewhat it holds
wordstringThe coinage itself. Embedded.
flavourstringOne of neobrand, latinate, germanic, slavic, nihongo, oceanic, semitic, steppe, elvish. Embedded.
pronunciationstringSyllable hint with the stressed syllable upper-cased, e.g. KA-vi-nor.
notestringWhatever you wrote next to it. Embedded, so it is what search by meaning actually keys off.
syllablesnumberSyllable count of the finished word.
seedstringThe seed of the batch it came from, so a kept word can be traced back to its siblings.
saved_attimestampISO-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
keymeaning
fflavour id
ssyllable range, min-max
xfamiliar-to-exotic dial, 0–100
nhow many words
sw / ewstarts-with / ends-with, letters only
cchecks: w dictionary, n names, b trademarks
seedthe seed — the same one always gives the same batch

The offensive-term check has no letter in c because it cannot be switched off.