Pivot Shelf saved-views API
Every chart and pivot is computed in your browser from the file you drop, so there is no job to submit and nothing to bill. What the API exposes is the one thing the app stores: the views a signed-in person chooses to save.
No run API, by design
Pivot Shelf has no model. Reading the file, running the SQL and drawing the chart all happen in the page, and your rows never leave the device. To get the same numbers in a script, copy the SQL from the SQL tab and run it in DuckDB or SQLite; to sync saved views, 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"}}
| Code | HTTP | What it means here |
|---|---|---|
UNAUTHORIZED | 401 | Missing, malformed or expired bearer token. |
FORBIDDEN | 403 | A valid token for a different app. |
NOT_FOUND | 404 | No such record visible to you. Rows belong to their owner, so someone else's record is a 404. |
VALIDATION_ERROR | 400 | A declared field has the wrong type, usually a saved_at that is not ISO-8601 with a Z. |
RATE_LIMITED | 429 | Too many requests; back off and retry. |
QUOTA_EXCEEDED | 409 | Too many records for one owner, or a document over 64 KB. |
The views collection
One record per saved view. Owner-read, user-write. A view is the shelf layout, never the data.
| Field | Type | Meaning |
|---|---|---|
name | string | The chart title, or the automatic title when none was typed. |
dataset | string | The file name the view was built on, or sample:orders, sample:weather, sample:homes. |
mark | string | auto, bar, line, area, scatter, heatmap or pivot. |
columns | string | The table's column names, comma-joined, so a client can tell which tables the view fits. |
view_json | string | The view itself as JSON (format below). |
saved_at | timestamp | ISO-8601 with a Z. |
The view format
x (Columns), y (Rows), color and size are lists of items {field, agg?, bin?, width?}. agg is one of sum avg median min max count countd; leave it out to group by the field. bin is one of year quarter month week day hour month_of_year weekday for dates, or bin with a width for numbers. The field * is the row count. filters holds {field, kind:"in", values, exclude} or {field, kind:"range", min, max}; calc holds {name, expr} calculated fields written as SQL expressions. The same JSON travels in a share link as URL-safe base64 after #view=, and is what View .json downloads.
{
"v": 1,
"mark": "bar",
"aggregate": true,
"x": [
{
"field": "region"
}
],
"y": [
{
"field": "revenue",
"agg": "sum"
}
],
"color": [
{
"field": "segment"
}
],
"size": [],
"filters": [
{
"field": "category",
"kind": "in",
"values": [
"Office"
],
"exclude": false
}
],
"calc": [],
"opts": {
"stack": "stacked",
"sort": "desc",
"labels": false,
"shade": true
},
"title": "Office revenue by region"
}
Steps
1 Get a token
A guest token needs no credentials and owns its own saved views. 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
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html, or step 1
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/guest",
method="POST",
)
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",
});
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("POST", "https://api.skillsafe.ai/v1/app-api/guest", body)
_ = 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/guest"))
.method("POST", 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/guest")
req = Net::HTTP::Post.new(uri)
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_HTTPHEADER, ["Accept: 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");
var res = await client.SendAsync(req);
Console.WriteLine(await res.Content.ReadAsStringAsync());
Returns:
{"data":{"token":"aut_...","subject_type":"guest","subject_id":"gst_..."}}
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 $PIVOT_SHELF_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 view
The same record the app writes when a signed-in person presses Save view. view_json is the shelf layout as a JSON string (format below); it never holds rows. saved_at is a declared timestamp and must be ISO-8601 with a Z. Keep a document under 64 KB.
POST https://api.skillsafe.ai/v1/app-api/collections/views/records
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/views/records \
-H "Authorization: Bearer $PIVOT_SHELF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"doc": {"name": "Office revenue by region", "dataset": "sales_2025.csv", "mark": "bar", "columns": "order_date,region,segment,category,revenue", "view_json": "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at": "2026-01-02T09:00:00Z"}}'
import json, urllib.request
TOKEN = "YOUR_TOKEN" # from /tokens.html, or step 1
body = json.dumps({"doc": {"name": "Office revenue by region", "dataset": "sales_2025.csv", "mark": "bar", "columns": "order_date,region,segment,category,revenue", "view_json": "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at": "2026-01-02T09:00:00Z"}}).encode()
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/collections/views/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/views/records", {
method: "POST",
headers: { Authorization: "Bearer " + TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"doc": {"name": "Office revenue by region", "dataset": "sales_2025.csv", "mark": "bar", "columns": "order_date,region,segment,category,revenue", "view_json": "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at": "2026-01-02T09: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": {"name": "Office revenue by region", "dataset": "sales_2025.csv", "mark": "bar", "columns": "order_date,region,segment,category,revenue", "view_json": "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at": "2026-01-02T09:00:00Z"}}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/views/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/views/records"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"doc\": {\"name\": \"Office revenue by region\", \"dataset\": \"sales_2025.csv\", \"mark\": \"bar\", \"columns\": \"order_date,region,segment,category,revenue\", \"view_json\": \"{\\\"v\\\":1,\\\"mark\\\":\\\"bar\\\",\\\"aggregate\\\":true,\\\"x\\\":[{\\\"field\\\":\\\"region\\\"}],\\\"y\\\":[{\\\"field\\\":\\\"revenue\\\",\\\"agg\\\":\\\"sum\\\"}],\\\"color\\\":[{\\\"field\\\":\\\"segment\\\"}],\\\"size\\\":[],\\\"filters\\\":[{\\\"field\\\":\\\"category\\\",\\\"kind\\\":\\\"in\\\",\\\"values\\\":[\\\"Office\\\"],\\\"exclude\\\":false}],\\\"calc\\\":[],\\\"opts\\\":{\\\"stack\\\":\\\"stacked\\\",\\\"sort\\\":\\\"desc\\\",\\\"labels\\\":false,\\\"shade\\\":true},\\\"title\\\":\\\"Office revenue by region\\\"}\", \"saved_at\": \"2026-01-02T09: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/views/records")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"doc" => {"name" => "Office revenue by region", "dataset" => "sales_2025.csv", "mark" => "bar", "columns" => "order_date,region,segment,category,revenue", "view_json" => "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at" => "2026-01-02T09: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/views/records");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"doc": {"name": "Office revenue by region", "dataset": "sales_2025.csv", "mark": "bar", "columns": "order_date,region,segment,category,revenue", "view_json": "{\"v\":1,\"mark\":\"bar\",\"aggregate\":true,\"x\":[{\"field\":\"region\"}],\"y\":[{\"field\":\"revenue\",\"agg\":\"sum\"}],\"color\":[{\"field\":\"segment\"}],\"size\":[],\"filters\":[{\"field\":\"category\",\"kind\":\"in\",\"values\":[\"Office\"],\"exclude\":false}],\"calc\":[],\"opts\":{\"stack\":\"stacked\",\"sort\":\"desc\",\"labels\":false,\"shade\":true},\"title\":\"Office revenue by region\"}", "saved_at": "2026-01-02T09: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/views/records");
req.Headers.Add("Authorization", "Bearer " + token);
req.Content = new StringContent("{\"doc\": {\"name\": \"Office revenue by region\", \"dataset\": \"sales_2025.csv\", \"mark\": \"bar\", \"columns\": \"order_date,region,segment,category,revenue\", \"view_json\": \"{\\\"v\\\":1,\\\"mark\\\":\\\"bar\\\",\\\"aggregate\\\":true,\\\"x\\\":[{\\\"field\\\":\\\"region\\\"}],\\\"y\\\":[{\\\"field\\\":\\\"revenue\\\",\\\"agg\\\":\\\"sum\\\"}],\\\"color\\\":[{\\\"field\\\":\\\"segment\\\"}],\\\"size\\\":[],\\\"filters\\\":[{\\\"field\\\":\\\"category\\\",\\\"kind\\\":\\\"in\\\",\\\"values\\\":[\\\"Office\\\"],\\\"exclude\\\":false}],\\\"calc\\\":[],\\\"opts\\\":{\\\"stack\\\":\\\"stacked\\\",\\\"sort\\\":\\\"desc\\\",\\\"labels\\\":false,\\\"shade\\\":true},\\\"title\\\":\\\"Office revenue by region\\\"}\", \"saved_at\": \"2026-01-02T09: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":{"name":"Office revenue by region", ...}}}}
4 List saved views
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, and JSON.parse(doc.view_json) to get the view.
POST https://api.skillsafe.ai/v1/app-api/collections/views/query
curl -s -X POST https://api.skillsafe.ai/v1/app-api/collections/views/query \
-H "Authorization: Bearer $PIVOT_SHELF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"where": {"dataset": {"eq": "sales_2025.csv"}}, "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": {"dataset": {"eq": "sales_2025.csv"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}).encode()
req = urllib.request.Request(
"https://api.skillsafe.ai/v1/app-api/collections/views/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/views/query", {
method: "POST",
headers: { Authorization: "Bearer " + TOKEN, "Content-Type": "application/json" },
body: JSON.stringify({"where": {"dataset": {"eq": "sales_2025.csv"}}, "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": {"dataset": {"eq": "sales_2025.csv"}}, "sort": {"field": "saved_at", "dir": "desc"}, "limit": 20}`)
req, _ := http.NewRequest("POST", "https://api.skillsafe.ai/v1/app-api/collections/views/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/views/query"))
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"where\": {\"dataset\": {\"eq\": \"sales_2025.csv\"}}, \"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/views/query")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{token}"
req["Content-Type"] = "application/json"
req.body = {"where" => {"dataset" => {"eq" => "sales_2025.csv"}}, "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/views/query");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"where": {"dataset": {"eq": "sales_2025.csv"}}, "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/views/query");
req.Headers.Add("Authorization", "Bearer " + token);
req.Content = new StringContent("{\"where\": {\"dataset\": {\"eq\": \"sales_2025.csv\"}}, \"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":{"name":"Office revenue by region","mark":"bar", ...}}]},"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/views/records/rec_REPLACE_ME
curl -s -X DELETE https://api.skillsafe.ai/v1/app-api/collections/views/records/rec_REPLACE_ME \
-H "Authorization: Bearer $PIVOT_SHELF_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/views/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/views/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/views/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/views/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/views/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/views/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/views/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}}