Auth Desk API
Everything the web app does over the model is one HTTP call. Base URL https://api.skillsafe.ai/v1/app-api. Every request carries Authorization: Bearer <token> and every response uses the same envelope.
The response envelope
Success is {"ok": true, "data": {...}}. Failure is {"ok": false, "error": {"code": "...", "message": "...", "details": {...}}}. Always branch on ok, never on the HTTP status alone.
| Code | HTTP | What it means here |
|---|---|---|
UNAUTHORIZED | 401 | Missing, malformed or expired token. Mint a new one on the token page. |
PAYMENT_REQUIRED | 402 | Balance below min_credits for this lane. Call /estimate first and top up. |
VALIDATION_ERROR | 400 | The input object is the wrong shape — usually a missing config or an unknown task. |
RATE_LIMITED | 429 | Back off and retry. Do not tight-loop. |
NOT_FOUND | 404 | Wrong job id, or a job that belongs to another subject. |
INTERNAL | 500 | Retry once with the same idempotency key. |
The task field comes first
Auth Desk is one app with four lanes over one work object. Every request must set task; it selects the lane, the prompt section, the output body shape and the price. If task is missing the model picks the closest lane and reports lane_inferred: true — usable, but never what you want from a script.
task | What that lane returns |
|---|---|
audit | Review the whole configuration and return an ordered hardening plan plus the corrected auth.ts. |
password | Build out the credential surface: policy, all five flows, and the transactional emails. |
twofactor | Mount and wire the twoFactor plugin: methods, enrolment, recovery, client calls. |
organization | Design roles, resources, a complete access matrix and the invitation flow. |
Input fields
Taken from readForm() in app.js — this is exactly what the web app sends.
| Field | Type | Required | Notes |
|---|---|---|---|
task | string | yes | One of audit, password, twofactor, organization. |
config | string | yes | The pasted setup. Separate multiple files with a // file: name.ts line. Clipped from the middle at 48,000 characters, both ends kept. |
notes | string | no | Free text about the product. The organization lane leans on it heavily; send it empty otherwise. |
framework | string | no | The framework you believe it is, or "unknown". |
prescan | object | no | The browser prescan. Omit it and the model simply has fewer facts — but then coverage_check comes back empty, because there are no flags to reconcile. |
clip_note | string | no | Send it when you clipped config yourself, so the model writes around the gap. |
retry_note | string | no | Send it on a second attempt when the first reply did not parse, naming what was wrong. The web app sets it automatically and reuses an idempotency key with the attempt counter bumped, so the retry is a new run rather than a duplicate charge. |
redaction_note | string | no | Send it when you stripped a literal secret out of config before sending. The web app always does: the auth secret, any OAuth clientSecret and any password inside a database URL are replaced with a marker, and the note tells the model the values are compromised and must not be reconstructed. |
Step 1 — get a token
Open the token page, reveal your token and copy the shell export. It is the same token the web app holds in this browser, so a script and the page share one identity, one balance and one history. Keep it out of source control — export it as an environment variable and read it from there, the way every sample below does.
Step 2 — confirm the session and the balance
GET /me is free. It tells you whether the token is a personal or a guest subject and how many credits it can spend.
curl -sS "https://api.skillsafe.ai/v1/app-api/me" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN"import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
req = urllib.request.Request("https://api.skillsafe.ai/v1/app-api/me",
headers={"Authorization": "Bearer " + TOKEN})
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/me", {
headers: { Authorization: `Bearer ${TOKEN}` }
});
console.log(await res.json());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/me", nil)
req.Header.Set("Authorization", "Bearer YOUR_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.*;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/me"))
.header("Authorization", "Bearer YOUR_TOKEN")
.GET().build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'uri'
uri = URI('https://api.skillsafe.ai/v1/app-api/me')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/me");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_TOKEN"],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_TOKEN");
Console.WriteLine(await http.GetStringAsync("https://api.skillsafe.ai/v1/app-api/me"));Step 3 — estimate before you spend
POST /estimate is free and charges nothing. It returns model, model_alias, markup_bps, hold_credits and min_credits. The hold differs per lane, so estimate the lane you are about to run — never reuse another lane’s number.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/estimate" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"task": "audit",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "",
"framework": "unknown"
}'import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
body = {
"task": "audit",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "",
"framework": "unknown"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/estimate",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/estimate", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"task": "audit",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "",
"framework": "unknown"
})
});
console.log(await res.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{"task": "audit", "config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});", "notes": "", "framework": "unknown"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/estimate", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
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.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String body = """
{
"task": "audit",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "",
"framework": "unknown"
}
""";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/estimate"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
require 'uri'
token = 'YOUR_TOKEN'
uri = URI('https://api.skillsafe.ai/v1/app-api/estimate')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{token}"
req['Content-Type'] = 'application/json'
req.body = {
'task': 'audit',
'config': 'import { betterAuth } from \'better-auth\';\nimport { drizzleAdapter } from \'better-auth/adapters/drizzle\';\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \'pg\' }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});',
'notes': '',
'framework': 'unknown'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$token = "YOUR_TOKEN";
$body = <<<JSON
{
"task": "audit",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "",
"framework": "unknown"
}
JSON;
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/estimate");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token",
"Content-Type: application/json",
],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var body = @"{""task"": ""audit"", ""config"": ""import { betterAuth } from \""better-auth\"";\nimport { drizzleAdapter } from \""better-auth/adapters/drizzle\"";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \""pg\"" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});"", ""notes"": """", ""framework"": ""unknown""}";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(body, Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/estimate", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());Step 4 — run, then poll
POST /run returns {"job_id": "..."} immediately. Poll GET /jobs/{job_id} until status is terminal. Always send an Idempotency-Key header derived from the lane plus a hash of the input plus an attempt counter: a retried request with the same key returns the original job instead of billing you twice.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: auth-desk:password:9f3a2c:a1" \
-d '{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}'import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
body = {
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
})
});
console.log(await res.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{"task": "password", "config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});", "notes": "Consumer product, open sign-up, mail through Resend.", "framework": "unknown"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
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.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String body = """
{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
""";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
require 'uri'
token = 'YOUR_TOKEN'
uri = URI('https://api.skillsafe.ai/v1/app-api/run')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{token}"
req['Content-Type'] = 'application/json'
req.body = {
'task': 'password',
'config': 'import { betterAuth } from \'better-auth\';\nimport { drizzleAdapter } from \'better-auth/adapters/drizzle\';\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \'pg\' }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});',
'notes': 'Consumer product, open sign-up, mail through Resend.',
'framework': 'unknown'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$token = "YOUR_TOKEN";
$body = <<<JSON
{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
JSON;
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token",
"Content-Type: application/json",
],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var body = @"{""task"": ""password"", ""config"": ""import { betterAuth } from \""better-auth\"";\nimport { drizzleAdapter } from \""better-auth/adapters/drizzle\"";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \""pg\"" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});"", ""notes"": ""Consumer product, open sign-up, mail through Resend."", ""framework"": ""unknown""}";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(body, Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());Then poll the job:
curl -sS "https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN"import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
req = urllib.request.Request("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID",
headers={"Authorization": "Bearer " + TOKEN})
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID", {
headers: { Authorization: `Bearer ${TOKEN}` }
});
console.log(await res.json());package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID", nil)
req.Header.Set("Authorization", "Bearer YOUR_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.*;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID"))
.header("Authorization", "Bearer YOUR_TOKEN")
.GET().build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'uri'
uri = URI('https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer YOUR_TOKEN'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer YOUR_TOKEN"],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_TOKEN");
Console.WriteLine(await http.GetStringAsync("https://api.skillsafe.ai/v1/app-api/jobs/JOB_ID"));Step 5 — stream instead
POST /run-stream is the same call over Server-Sent Events. The web app uses it so the staged progress card can advance on real signals in the delta stream. The same Idempotency-Key rule applies.
curl -sS -N -X POST "https://api.skillsafe.ai/v1/app-api/run-stream" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "Idempotency-Key: auth-desk:password:9f3a2c:a1" \
-d '{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}'import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
body = {
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run-stream",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json"},
method="POST")
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run-stream", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
})
});
console.log(await res.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{"task": "password", "config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});", "notes": "Consumer product, open sign-up, mail through Resend.", "framework": "unknown"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run-stream", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
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.*;
public class Main {
public static void main(String[] args) throws Exception {
String token = "YOUR_TOKEN";
String body = """
{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
""";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run-stream"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'json'
require 'uri'
token = 'YOUR_TOKEN'
uri = URI('https://api.skillsafe.ai/v1/app-api/run-stream')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = "Bearer #{token}"
req['Content-Type'] = 'application/json'
req.body = {
'task': 'password',
'config': 'import { betterAuth } from \'better-auth\';\nimport { drizzleAdapter } from \'better-auth/adapters/drizzle\';\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \'pg\' }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});',
'notes': 'Consumer product, open sign-up, mail through Resend.',
'framework': 'unknown'
}.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$token = "YOUR_TOKEN";
$body = <<<JSON
{
"task": "password",
"config": "import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth/adapters/drizzle\";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \"pg\" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});",
"notes": "Consumer product, open sign-up, mail through Resend.",
"framework": "unknown"
}
JSON;
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run-stream");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $body,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token",
"Content-Type: application/json",
],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var token = "YOUR_TOKEN";
var body = @"{""task"": ""password"", ""config"": ""import { betterAuth } from \""better-auth\"";\nimport { drizzleAdapter } from \""better-auth/adapters/drizzle\"";\n\nexport const auth = betterAuth({\n database: drizzleAdapter(db, { provider: \""pg\"" }),\n emailAndPassword: { enabled: true, minPasswordLength: 6 },\n plugins: [twoFactor()]\n});"", ""notes"": ""Consumer product, open sign-up, mail through Resend."", ""framework"": ""unknown""}";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var content = new StringContent(body, Encoding.UTF8, "application/json");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run-stream", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());Step 6 — the other two lanes
Every worked example above uses task: "audit" or task: "password", so here are the remaining two end to end. Nothing about the call changes — same endpoint, same envelope, same Idempotency-Key rule — only task, the price, and the shape of body in the reply.
task: "twofactor"
Returns body.methods, body.enrolment, body.recovery and body.client_calls. The artifact is usually a corrected src/lib/auth-client.ts rather than auth.ts, so merge it into your paste by file name instead of overwriting the server config.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: auth-desk:twofactor:4c81ab:a1" \
-d '{
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}'import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
body = {
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json",
"Idempotency-Key": "auth-desk:twofactor:4c81ab:a1"},
method="POST")
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": "auth-desk:twofactor:4c81ab:a1"
},
body: JSON.stringify({
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
})
});
console.log(await res.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{"task": "twofactor", "config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });", "notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.", "framework": "Next.js"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "auth-desk:twofactor:4c81ab:a1")
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.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}
""";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer YOUR_TOKEN")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "auth-desk:twofactor:4c81ab:a1")
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.skillsafe.ai/v1/app-api/run')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = 'Bearer YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req['Idempotency-Key'] = 'auth-desk:twofactor:4c81ab:a1'
req.body = <<~JSON
{
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}
JSON
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$body = <<<'JSON'
{
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}
JSON;
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_TOKEN",
"Content-Type: application/json",
"Idempotency-Key: auth-desk:twofactor:4c81ab:a1",
],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var body = """
{
"task": "twofactor",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { twoFactor } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true, requireEmailVerification: true },\n plugins: [twoFactor()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\n\nexport const authClient = createAuthClient({ plugins: [] });",
"notes": "Enterprise customers want MFA. Transactional email only, no SMS provider.",
"framework": "Next.js"
}
""";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_TOKEN");
var content = new StringContent(body, Encoding.UTF8, "application/json");
content.Headers.Add("Idempotency-Key", "auth-desk:twofactor:4c81ab:a1");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());task: "organization"
Returns body.roles, body.resources, body.matrix, body.invitation_flow and body.gaps. This is the one lane where notes genuinely changes the answer: it is designing an access-control model, not reading one, so say who the tenants are and which role names you already use.
curl -sS -X POST "https://api.skillsafe.ai/v1/app-api/run" \
-H "Authorization: Bearer $AUTH_DESK_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: auth-desk:organization:7d20fe:a1" \
-d '{
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}'import json, urllib.request, os
TOKEN = os.environ.get("AUTH_DESK_TOKEN", "YOUR_TOKEN")
body = {
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/run",
data=json.dumps(body).encode(),
headers={"Authorization": "Bearer " + TOKEN,
"Content-Type": "application/json",
"Idempotency-Key": "auth-desk:organization:7d20fe:a1"},
method="POST")
print(json.load(urllib.request.urlopen(req)))const TOKEN = "YOUR_TOKEN";
const res = await fetch("https://api.skillsafe.ai/v1/app-api/run", {
method: "POST",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
"Idempotency-Key": "auth-desk:organization:7d20fe:a1"
},
body: JSON.stringify({
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
})
});
console.log(await res.json());package main
import (
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_TOKEN"
body := []byte(`{"task": "organization", "config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });", "notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.", "framework": "Next.js"}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/run", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "auth-desk:organization:7d20fe:a1")
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.*;
public class Main {
public static void main(String[] args) throws Exception {
String body = """
{
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}
""";
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.skillsafe.ai/v1/app-api/run"))
.header("Authorization", "Bearer YOUR_TOKEN")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "auth-desk:organization:7d20fe:a1")
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}require 'net/http'
require 'uri'
require 'json'
uri = URI('https://api.skillsafe.ai/v1/app-api/run')
req = Net::HTTP::Post.new(uri)
req['Authorization'] = 'Bearer YOUR_TOKEN'
req['Content-Type'] = 'application/json'
req['Idempotency-Key'] = 'auth-desk:organization:7d20fe:a1'
req.body = <<~JSON
{
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}
JSON
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
puts res.body<?php
$body = <<<'JSON'
{
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}
JSON;
$ch = curl_init("https://api.skillsafe.ai/v1/app-api/run");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_TOKEN",
"Content-Type: application/json",
"Idempotency-Key: auth-desk:organization:7d20fe:a1",
],
]);
echo curl_exec($ch);using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
var body = """
{
"task": "organization",
"config": "import { betterAuth } from \"better-auth\";\nimport { prismaAdapter } from \"better-auth/adapters/prisma\";\nimport { organization } from \"better-auth/plugins\";\n\nexport const auth = betterAuth({\n secret: env.BETTER_AUTH_SECRET,\n baseURL: \"https://console.northwind.dev\",\n database: prismaAdapter(prisma, { provider: \"postgresql\" }),\n emailAndPassword: { enabled: true },\n plugins: [organization()]\n});\n\n// file: src/lib/auth-client.ts\nimport { createAuthClient } from \"better-auth/react\";\nimport { organizationClient } from \"better-auth/client/plugins\";\n\nexport const authClient = createAuthClient({ plugins: [organizationClient()] });",
"notes": "B2B SaaS. Customers are companies with projects and one monthly invoice. We already say workspace owner, admin and engineer internally; support staff need read-only access to a customer's projects without being members; billing is the owner's alone.",
"framework": "Next.js"
}
""";
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_TOKEN");
var content = new StringContent(body, Encoding.UTF8, "application/json");
content.Headers.Add("Idempotency-Key", "auth-desk:organization:7d20fe:a1");
var res = await http.PostAsync("https://api.skillsafe.ai/v1/app-api/run", content);
Console.WriteLine(await res.Content.ReadAsStringAsync());The output contract
Every lane returns one JSON object with the same envelope. This is the shape normalize() in app.js enforces — a reply that does not match it is retried once with a reformat instruction, then shown raw.
{
"lane": "audit | password | twofactor | organization",
"lane_inferred": false,
"title": "string",
"posture": "ship-ready | harden-first | not-production-safe",
"verdict": "one sentence",
"stack": "string",
"adapter": "string",
"summary": "string",
"assumptions": ["string"],
"open_questions": ["string"],
"findings": [
{
"id": "AD-001",
"title": "string",
"severity": "critical | high | medium | low",
"area": "secrets | database | session | cookies | email | oauth | plugins | rate-limit | rbac | wiring",
"file": "string",
"line": 0,
"evidence": "verbatim lines from the paste",
"why": "string",
"fix": "string",
"fix_code": "TypeScript"
}
],
"coverage_check": [
{"flag_id": "BA-NO-RATELIMIT", "status": "confirmed | set-aside | superseded",
"finding_id": "AD-003", "note": "string"}
],
"artifact": {"kind": "none | typescript | markdown", "filename": "auth.ts", "content": "string"},
"next_lane": {"lane": "twofactor", "reason": "string"},
"body": { }
}
Per-lane body
task: "audit"
"body": {
"hardening_plan": [
{"title": "string", "why": "string", "risk": "critical|high|medium|low",
"effort": "minutes|an hour|a day", "code": "TypeScript"}
],
"parity_notes": [{"plugin": "organization", "client_plugin": "organizationClient", "note": "string"}],
"residual_risks": ["string"]
}
task: "password"
"body": {
"policy": {
"min_length": "8", "min_length_why": "string",
"max_length": "128", "max_length_why": "string",
"require_verification": "true", "verification_why": "string",
"auto_sign_in": "false", "auto_sign_in_why": "string",
"hashing": "library default (scrypt)", "hashing_why": "string"
},
"flows": [
{"name": "sign-up", "status": "complete|partial|missing",
"steps": ["string"], "gaps": ["string"], "code": "TypeScript"}
],
"emails": [{"name": "Verify your email", "subject": "string", "body": "string"}]
}
All five flows always appear: sign-up, verify email, sign-in, reset password, change password.
Every policy value is a STRING, so that "library default" is expressible.
task: "twofactor"
"body": {
"methods": [{"method": "TOTP", "status": "present|weak|missing|n-a", "detail": "string"}],
"enrolment": [{"step": "string", "description": "string", "code": "TypeScript"}],
"recovery": {
"backup_codes": "string", "backup_codes_note": "string",
"trusted_devices": "string", "trusted_devices_note": "string",
"note": "what a user does when they lose the device"
},
"client_calls": [{"call": "authClient.twoFactor.enable({ password })", "purpose": "string"}]
}
task: "organization"
"body": {
"roles": [{"role": "owner", "inherits": "admin", "permissions": ["project:delete"]}],
"resources": [{"resource": "project", "actions": ["create", "read", "update", "delete"]}],
"matrix": [{"role": "member", "resource": "project", "actions": ["read"]}],
"invitation_flow": [{"step": "string", "description": "string", "code": "TypeScript"}],
"gaps": ["string"]
}
The matrix is COMPLETE: one row per (role, resource) pair. A pair with no access carries an
empty actions array rather than being omitted.
The reconciliation rule
coverage_check carries exactly one entry per prescan flag id you sent and no entries for ids you did not send. A flag with no entry is rendered to the user as not accounted for. If you are scripting Auth Desk, this is the assertion worth writing: the set of flag_id values in the reply must equal the set of id values in prescan.flags, and every confirmed entry must name a finding_id that exists in findings.
Auth Desk is a derived work built on four @better-auth/skills — best-practices, emailandpassword, twofactor and organization. It is not affiliated with, endorsed by, or operated by the Better Auth project.