← Internal Link Finder / Data API

Internal Link Finder data API

Link suggestions are computed in your browser by an on-device embedding model, so there is no job to submit and nothing to bill. What the API exposes is the one thing the app stores: the link plans a person chooses to save.

No run API, by design

Internal Link Finder has no server-side model. Parsing, embedding (all-MiniLM-L6-v2 with ONNX Runtime Web) and anchor scoring all happen in the page, and your pages never leave the device. To reproduce the method in a script, embed titles and sentences with the same open model and follow the steps in llms.txt; to sync saved plans, use the endpoints below. They cost nothing to call.

Base URL and envelope

https://api.skillsafe.ai/v1/app-api

Every response is a JSON envelope. Success carries data; failure carries error with a stable code.

{"ok":true,"data":{ ... }}
{"ok":false,"error":{"code":"NOT_FOUND","message":"no such record"}}
CodeHTTPWhat it means here
UNAUTHORIZED401Missing, malformed or expired bearer token.
FORBIDDEN403A valid token for a different app.
NOT_FOUND404No such record visible to you. Rows belong to their owner, so someone else's record is a 404.
VALIDATION_ERROR400A declared field has the wrong type, usually a saved_at that is not ISO-8601 with a Z.
RATE_LIMITED429Too many requests; back off and retry.
QUOTA_EXCEEDED409Too many records for one owner, or a document over 64 KB.

The plans collection

One record per saved link plan. Owner-read, user-write. Page text is never stored.

FieldTypeMeaning
labelstringA name for the plan, e.g. "Link plan: blog section".
hoststringThe most common host among the loaded URLs, or empty.
pagesnumberPages analysed.
linksnumberLinks in the plan.
orphansnumberPages that receive no suggested inbound link.
min_simnumberThe similarity floor the plan was built with.
plan_csvstringThe CSV export: source_url, source_title, target_url, target_title, similarity, anchor_text, anchor_confidence, alternative_anchors, source_sentence, sentence_with_link_html, already_linked. Clipped on whole rows.
summary_mdstringThe Markdown plan, clipped on whole lines.
saved_attimestampISO-8601 with a Z.

Steps

1 Get a token

A guest token needs no credentials (the body names the app) and owns its own saved plans. To use the identity your browser already has, copy the token from the session page instead; every later step is the same.

POST https://api.skillsafe.ai/v1/app-api/guest
curl -s -X POST https://api.skillsafe.ai/v1/app-api/guest \
  -H "Content-Type: application/json" \
  -d '{"slug": "internal-link-finder"}'
import json, urllib.request

TOKEN = "YOUR_TOKEN"   # from /tokens.html, or step 1
body = json.dumps({"slug": "internal-link-finder"}).encode()
req = urllib.request.Request(
    "https://api.skillsafe.ai/v1/app-api/guest",
    method="POST",
    data=body,
    headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
    print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";   // from /tokens.html, or step 1

const res = await fetch("https://api.skillsafe.ai/v1/app-api/guest", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({"slug": "internal-link-finder"}),
});
const { data, error } = await res.json();
if (error) throw new Error(error.code);
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	token := "YOUR_TOKEN" // from /tokens.html, or step 1
	body := strings.NewReader(`{"slug": "internal-link-finder"}`)
	req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/guest", body)
	_ = token
	req.Header.Set("Content-Type", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String token = "YOUR_TOKEN";   // from /tokens.html, or step 1
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.skillsafe.ai/v1/app-api/guest"))
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("{\"slug\": \"internal-link-finder\"}"))
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "json"
require "net/http"

token = "YOUR_TOKEN"   # from /tokens.html, or step 1
uri = URI("https://api.skillsafe.ai/v1/app-api/guest")
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = "application/json"
req.body = {"slug" => "internal-link-finder"}.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, or step 1

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.skillsafe.ai/v1/app-api/guest");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"slug": "internal-link-finder"}');
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json", "Content-Type: application/json"]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);
using System.Net.Http;
using System.Text;

var token = "YOUR_TOKEN";   // from /tokens.html, or step 1
using var client = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"), "https://api.skillsafe.ai/v1/app-api/guest");
req.Content = new StringContent("{\"slug\": \"internal-link-finder\"}", Encoding.UTF8, "application/json");
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Returns:

{"data":{"token":"aut_...","guest_id":"gst_...","expires_at":"..."}}

2 See who the token is

Three fields: subject_type, subject_id and credits. The signed-in test is subject_type == "user". Nothing on this app spends credits.

GET https://api.skillsafe.ai/v1/app-api/me
curl -s -X GET https://api.skillsafe.ai/v1/app-api/me \
  -H "Authorization: Bearer $LINK_FINDER_TOKEN"
import json, urllib.request

TOKEN = "YOUR_TOKEN"   # from /tokens.html, or step 1
req = urllib.request.Request(
    "https://api.skillsafe.ai/v1/app-api/me",
    method="GET",
    headers={"Authorization": "Bearer " + TOKEN},
)
with urllib.request.urlopen(req) as r:
    print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";   // from /tokens.html, or step 1

const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
  method: "GET",
  headers: { Authorization: "Bearer " + TOKEN },
});
const { data, error } = await res.json();
if (error) throw new Error(error.code);
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	token := "YOUR_TOKEN" // from /tokens.html, or step 1
	body := strings.NewReader("")
	req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", body)
	req.Header.Set("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String token = "YOUR_TOKEN";   // from /tokens.html, or step 1
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
    .header("Authorization", "Bearer " + token)
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "json"
require "net/http"

token = "YOUR_TOKEN"   # from /tokens.html, or step 1
uri = URI("https://api.skillsafe.ai/v1/app-api/me")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{token}"
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, or step 1

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.skillsafe.ai/v1/app-api/me");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json", "Authorization: Bearer $token"]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);
using System.Net.Http;
using System.Text;

var token = "YOUR_TOKEN";   // from /tokens.html, or step 1
using var client = 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 client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Returns:

{"data":{"subject_type":"user","subject_id":"usr_...","credits":12000}}

3 Save a link plan

The same record the app writes when you press Save plan to my account. saved_at is a declared timestamp and must be ISO-8601 with a Z. plan_csv is the exported CSV (the app clips it on whole rows to stay well under the 64 KB document cap).

POST https://api.skillsafe.ai/v1/app-api/collections/plans/records
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/plans/records \
  -H "Authorization: Bearer $LINK_FINDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"doc": {"label": "Link plan: blog section", "host": "example.com", "pages": 42, "links": 96, "orphans": 3, "min_sim": 0.4, "plan_csv": "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md": "# Internal link plan\n...", "saved_at": "2026-10-30T09:00:00Z"}}'
import json, urllib.request

TOKEN = "YOUR_TOKEN"   # from /tokens.html, or step 1
body = json.dumps({"doc": {"label": "Link plan: blog section", "host": "example.com", "pages": 42, "links": 96, "orphans": 3, "min_sim": 0.4, "plan_csv": "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md": "# Internal link plan\n...", "saved_at": "2026-10-30T09:00:00Z"}}).encode()
req = urllib.request.Request(
    "https://api.skillsafe.ai/v1/app-api/collections/plans/records",
    method="POST",
    data=body,
    headers={"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
    print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";   // from /tokens.html, or step 1

const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/plans/records", {
  method: "POST",
  headers: { Authorization: "Bearer " + TOKEN, "Content-Type": "application/json" },
  body: JSON.stringify({"doc": {"label": "Link plan: blog section", "host": "example.com", "pages": 42, "links": 96, "orphans": 3, "min_sim": 0.4, "plan_csv": "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md": "# Internal link plan\n...", "saved_at": "2026-10-30T09:00:00Z"}}),
});
const { data, error } = await res.json();
if (error) throw new Error(error.code);
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	token := "YOUR_TOKEN" // from /tokens.html, or step 1
	body := strings.NewReader(`{"doc": {"label": "Link plan: blog section", "host": "example.com", "pages": 42, "links": 96, "orphans": 3, "min_sim": 0.4, "plan_csv": "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md": "# Internal link plan\n...", "saved_at": "2026-10-30T09:00:00Z"}}`)
	req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/plans/records", 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()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String token = "YOUR_TOKEN";   // from /tokens.html, or step 1
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/plans/records"))
    .header("Authorization", "Bearer " + token)
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("{\"doc\": {\"label\": \"Link plan: blog section\", \"host\": \"example.com\", \"pages\": 42, \"links\": 96, \"orphans\": 3, \"min_sim\": 0.4, \"plan_csv\": \"source_url,source_title,target_url,target_title,similarity,anchor_text,...\\r\\n\", \"summary_md\": \"# Internal link plan\\n...\", \"saved_at\": \"2026-10-30T09:00:00Z\"}}"))
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "json"
require "net/http"

token = "YOUR_TOKEN"   # from /tokens.html, or step 1
uri = URI("https://api.skillsafe.ai/v1/app-api/collections/plans/records")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"doc" => {"label" => "Link plan: blog section", "host" => "example.com", "pages" => 42, "links" => 96, "orphans" => 3, "min_sim" => 0.4, "plan_csv" => "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md" => "# Internal link plan\n...", "saved_at" => "2026-10-30T09: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, or step 1

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.skillsafe.ai/v1/app-api/collections/plans/records");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"doc": {"label": "Link plan: blog section", "host": "example.com", "pages": 42, "links": 96, "orphans": 3, "min_sim": 0.4, "plan_csv": "source_url,source_title,target_url,target_title,similarity,anchor_text,...\r\n", "summary_md": "# Internal link plan\n...", "saved_at": "2026-10-30T09:00:00Z"}}');
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json", "Authorization: Bearer $token", "Content-Type: application/json"]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);
using System.Net.Http;
using System.Text;

var token = "YOUR_TOKEN";   // from /tokens.html, or step 1
using var client = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"), "https://api.skillsafe.ai/v1/app-api/collections/plans/records");
req.Headers.Add("Authorization", "Bearer " + token);
req.Content = new StringContent("{\"doc\": {\"label\": \"Link plan: blog section\", \"host\": \"example.com\", \"pages\": 42, \"links\": 96, \"orphans\": 3, \"min_sim\": 0.4, \"plan_csv\": \"source_url,source_title,target_url,target_title,similarity,anchor_text,...\\r\\n\", \"summary_md\": \"# Internal link plan\\n...\", \"saved_at\": \"2026-10-30T09:00:00Z\"}}", Encoding.UTF8, "application/json");
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Returns:

{"data":{"record":{"record_id":"rec_...","doc":{"label":"Link plan: blog section", ...}}}}

4 List saved plans

Every where entry is an operator object (eq ne lt lte gt gte in contains); the bare-value form is rejected. Sorting uses sort. Records come back as {record_id, doc}: read fields from doc.

POST https://api.skillsafe.ai/v1/app-api/collections/plans/query
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/plans/query \
  -H "Authorization: Bearer $LINK_FINDER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"where": {"host": {"eq": "example.com"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}'
import json, urllib.request

TOKEN = "YOUR_TOKEN"   # from /tokens.html, or step 1
body = json.dumps({"where": {"host": {"eq": "example.com"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}).encode()
req = urllib.request.Request(
    "https://api.skillsafe.ai/v1/app-api/collections/plans/query",
    method="POST",
    data=body,
    headers={"Authorization": "Bearer " + TOKEN, "Content-Type": "application/json"},
)
with urllib.request.urlopen(req) as r:
    print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";   // from /tokens.html, or step 1

const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/plans/query", {
  method: "POST",
  headers: { Authorization: "Bearer " + TOKEN, "Content-Type": "application/json" },
  body: JSON.stringify({"where": {"host": {"eq": "example.com"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}),
});
const { data, error } = await res.json();
if (error) throw new Error(error.code);
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	token := "YOUR_TOKEN" // from /tokens.html, or step 1
	body := strings.NewReader(`{"where": {"host": {"eq": "example.com"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}`)
	req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/plans/query", 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()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String token = "YOUR_TOKEN";   // from /tokens.html, or step 1
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/plans/query"))
    .header("Authorization", "Bearer " + token)
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("{\"where\": {\"host\": {\"eq\": \"example.com\"}}, \"sort\": {\"field\": \"saved_at\", \"dir\": \"desc\"}, \"limit\": 20}"))
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "json"
require "net/http"

token = "YOUR_TOKEN"   # from /tokens.html, or step 1
uri = URI("https://api.skillsafe.ai/v1/app-api/collections/plans/query")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"where" => {"host" => {"eq" => "example.com"}}, "sort" => {"field" => "saved_at", "dir" => "desc"}, "limit" => 20}.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, or step 1

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.skillsafe.ai/v1/app-api/collections/plans/query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"where": {"host": {"eq": "example.com"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}');
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json", "Authorization: Bearer $token", "Content-Type: application/json"]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);
using System.Net.Http;
using System.Text;

var token = "YOUR_TOKEN";   // from /tokens.html, or step 1
using var client = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("POST"), "https://api.skillsafe.ai/v1/app-api/collections/plans/query");
req.Headers.Add("Authorization", "Bearer " + token);
req.Content = new StringContent("{\"where\": {\"host\": {\"eq\": \"example.com\"}}, \"sort\": {\"field\": \"saved_at\", \"dir\": \"desc\"}, \"limit\": 20}", Encoding.UTF8, "application/json");
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Returns:

{"data":{"records":[{"record_id":"rec_...","doc":{"host":"example.com","links":96, ...}}]},"meta":{"pagination":{"has_more":false}}}

5 Delete one

Use the record_id from the create or query step. A record that is not yours returns 404, because rows are visible only to their owner.

DELETE https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME
curl -s -X DELETE https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME \
  -H "Authorization: Bearer $LINK_FINDER_TOKEN"
import json, urllib.request

TOKEN = "YOUR_TOKEN"   # from /tokens.html, or step 1
req = urllib.request.Request(
    "https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME",
    method="DELETE",
    headers={"Authorization": "Bearer " + TOKEN},
)
with urllib.request.urlopen(req) as r:
    print(json.load(r)["data"])
const TOKEN = "YOUR_TOKEN";   // from /tokens.html, or step 1

const res = await fetch("https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME", {
  method: "DELETE",
  headers: { Authorization: "Bearer " + TOKEN },
});
const { data, error } = await res.json();
if (error) throw new Error(error.code);
console.log(data);
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	token := "YOUR_TOKEN" // from /tokens.html, or step 1
	body := strings.NewReader("")
	req, _ := http.NewRequest("DELETE", "https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME", body)
	req.Header.Set("Authorization", "Bearer "+token)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
String token = "YOUR_TOKEN";   // from /tokens.html, or step 1
HttpRequest req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME"))
    .header("Authorization", "Bearer " + token)
    .method("DELETE", HttpRequest.BodyPublishers.noBody())
    .build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
require "json"
require "net/http"

token = "YOUR_TOKEN"   # from /tokens.html, or step 1
uri = URI("https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer #{token}"
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, or step 1

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Accept: application/json", "Authorization: Bearer $token"]);
$res = curl_exec($ch);
curl_close($ch);
print_r(json_decode($res, true)["data"]);
using System.Net.Http;
using System.Text;

var token = "YOUR_TOKEN";   // from /tokens.html, or step 1
using var client = new HttpClient();
var req = new HttpRequestMessage(new HttpMethod("DELETE"), "https://api.skillsafe.ai/v1/app-api/collections/plans/records/rec_REPLACE_ME");
req.Headers.Add("Authorization", "Bearer " + token);
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());

Returns:

{"data":{"deleted":true}}