We would like to use Google Analytics to see which pages people read and where signing up stalls. It sets cookies and sends your visit to Google, so it stays off unless you accept. What it records
One key.Every datasetyou were about to scrape.
softon.dev hosts independent data APIs — jobs, events, quotes, and whatever we ship next — behind one account, one key and one response envelope. Read the docs for one of them and you can read the docs for all of them.
Every API on softon.dev ships the same auth, pagination, filtering, error shape and rate-limit headers. New services drop into this grid — and into the docs — without a new mental model for anyone.
Job Data API
Live
Postings from company career pages and boards, deduplicated per source, with the employment type, job function and career level the source itself publishes, plus the full description on a detail fetch.
GET /v1/jobs
GET /v1/jobs/{id}
POST /v1/jobs/liveness
GET /v1/companies/{id}/jobs
183K postingsRefreshed every 15h · last 3hv1 · stable
Concerts, theatre, exhibitions and cinema with geocoded venues, and — where the source publishes them — per-showing schedules and remaining ticket counts recorded at every scrape.
Attributed quotations with author, category and collection provenance, plus a quote-of-the-day endpoint that is deterministic for a date, so it can be cached.
GET /v1/quotes
GET /v1/quotes/{id}
GET /v1/quotes/random
521K quotationsOne-time import · last 49dv1 · stable
The central bank's official daily AZN bulletin — 38 currencies and 4 bank metals — with the date you asked for AND the date the bank published, so a weekend's repeated numbers are marked as repeats rather than stored as a fresh publication.
Which release each upstream genome data source is on — RefSeq, dbSNP, 1000 Genomes/IGSR and NCBI Datasets — read from each source itself rather than hardcoded, with how the latest probe went beside it. Release markers only: no sequence, assembly or variant data.
Whether a watched URL answered us, why not if it did not, and how long it has been that way — with the vantage the request left from on every row. Status codes only: no page content is stored or served.
Two feeds that answer different halves of one question. CISA's Known Exploited Vulnerabilities catalog says what is being exploited NOW, with the Binding Operational Directive action and deadline attached to each CVE, and every published catalog version is kept so a past deadline is still answerable. FIRST's EPSS scores say what is LIKELY to be exploited next, as a probability for the coming 30 days, looked up by CVE id in batches of up to a hundred.
GET /v1/cyber/kev
GET /v1/cyber/kev/{id}
GET /v1/cyber/epss
GET /v1/cyber/epss/snapshot
383K catalogued entries and CVE scoresRefreshed daily · last 12hv1 · stable
Every response is { data, meta, error } with cursor pagination in meta.next and the same error codes. Write your client once; point it at any dataset. Rate limits and usage roll up to a single account.
Docs that grow themselves
Each service publishes a machine-readable schema at /v1/schema; the portal renders navigation, endpoint pages, field tables and snippets from it. Adding an API adds a docs section — nobody writes the boilerplate twice.
Server-rendered, so it's quick
The site and dashboard are Go templates with HTMX for the interactive parts: search, key management and live request testing return HTML fragments. No client bundle to ship, nothing to hydrate, works with JS off for the parts that matter.
Quickstart
Three minutes to the first row
Sign up, copy the key, call the endpoint. The same snippet works against every dataset in the catalog — swap the path and the filters. No SDK to install: these are the standard library in each language, and they run as they are.
req, _ := http.NewRequestWithContext(ctx, "GET",
"https://api.softon.dev/v1/jobs", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("SOFTON_KEY"))
q := req.URL.Query()
q.Set("q", "golang")
q.Set("remote", "true")
q.Set("updated_after", "2026-08-16T09:00:00Z")
req.URL.RawQuery = q.Encode()
res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close()
var page struct {
Data []Job `json:"data"`
Meta struct{ Next *string`json:"next"` } `json:"meta"`
}
if err := json.NewDecoder(res.Body).Decode(&page); err != nil { log.Fatal(err) }
// page.Meta.Next → send it back as ?cursor= for the following page
import json, os, urllib.parse, urllib.request
url = "https://api.softon.dev/v1/jobs" + "?" + urllib.parse.urlencode({
"q": "golang",
"remote": "true",
"updated_after": "2026-08-16T09:00:00Z",
})
req = urllib.request.Request(url, headers={
"Authorization": "Bearer " + os.environ["SOFTON_KEY"],
})
page = json.load(urllib.request.urlopen(req))
# page["meta"]["next"] → send it back as ?cursor= for the following page
const url = new URL("https://api.softon.dev/v1/jobs");
url.searchParams.set("q", "golang");
url.searchParams.set("remote", "true");
url.searchParams.set("updated_after", "2026-08-16T09:00:00Z");
const res = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.SOFTON_KEY}` },
});
if (!res.ok) thrownew Error(`${res.status} ${await res.text()}`);
const { data, meta } = await res.json();
// meta.next → send it back as ?cursor= for the following page
Every account starts free — 1,000 requests a month, all
live datasets, no card. The plans above are for when you outgrow it: pick one and we
will contact you to settle payment card to card. Already have an account? Ask from
your dashboard.
Every response is { data, meta, error }. Learn one dataset and you have learned all of them — and where a source publishes nothing, you get null rather than a plausible guess.
The response envelope — specified once, identical on every dataset.Built on softon.dev