Aurith

The shape of it. #

A working first request, copyable examples, and the API reference.

Beta. Outages are possible; see status.

Quick start · 2 minutes

# Make your first request

  1. 01Make an account and key on the account page; choose a plan on Services.
  2. 02Set AURITH_KEY in your environment. Keep it out of source control and URLs.
  3. 03Call GET /v1/items. Your key authorizes the request.
Terminal
curl "https://api.aurith.ca/v1/items?limit=1" \
  -H "Authorization: Bearer $AURITH_KEY"

# Response shape

{
  "items": [{
    "id": "itm_20260831T041233_8f2a1c",
    "lang": "en",
    "headline": "Example headline",
    "body": "The complete Aurith article text.",
    "source": "eia_today",
    "source_name": "Example publisher",
    "category": "business",
    "region": "us",
    "published_at": "2026-08-31T04:12:33Z",
    "held_at": "2026-08-31T04:15:31Z",
    "written_at": "2026-08-31T04:15:50Z",
    "latency_seconds": 178,
    "subjects": []
  }],
  "next": "itm_20260831T041233_8f2a1c",
  "held": 1
}

Values are illustrative. next is the last item id for a full page and null for a short page; held is the returned item count.

No key needed

# Try the public sample

Choose a category to call GET /v1/sample. The request is public and does not use your key.

Choose a category and run the sample.

The response will appear here.
Recipes

# Common patterns

# Poll once a minute without duplicates

Keep a since timestamp watermark, follow each page's next with cursor, and poll again after 60 seconds. Persist processed item ids and make processing idempotent so replaying the timestamp boundary cannot deliver an item twice.

Repeat every minute; paginate each response
LAST_HELD_AT=${LAST_HELD_AT:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}
while :; do
  curl --get "https://api.aurith.ca/v1/items" \
    --data-urlencode "since=$LAST_HELD_AT" \
    --data-urlencode "limit=50" \
    -H "Authorization: Bearer $AURITH_KEY"
  # Follow next as cursor until null; persist handled ids and the since watermark.
  sleep 60
done

# Page through everything

Omit cursor for the first request, then send the preceding page's next as cursor. Stop when next is null.

First page; repeat with cursor
curl "https://api.aurith.ca/v1/items?limit=200" \
  -H "Authorization: Bearer $AURITH_KEY"
# Repeat with cursor=<next> until next is null.

# Handle a 429

A 429 response carries Retry-After in seconds. Wait at least that long, then retry the same request; account keys share their rate limit.

Inspect headers
curl -i "https://api.aurith.ca/v1/items" \
  -H "Authorization: Bearer $AURITH_KEY"
# On 429, sleep for the Retry-After seconds before retrying.

# Ask for fewer fields

Use the comma-separated fields whitelist to return only properties you need. Unknown names return 400 naming fields.

Whitelist item properties
curl "https://api.aurith.ca/v1/items?fields=id,headline,source,held_at" \
  -H "Authorization: Bearer $AURITH_KEY"
Authentication

# Bearer key

Send Authorization: Bearer <key> on every key route; never put the key in the query string. Missing key: 401 No key. Send: Authorization: Bearer key>. Invalid, revoked or inactive key: 401 That key is not current. Account routes require the signed-in Supabase session token instead.

Key endpoint

# GET /v1/items

Return newest items available to this key. Repeated filters are comma-separated; omitted filters use saved settings and then key entitlements.

ParameterType / defaultPurpose and bounds
langstringComma-separated language codes; saved setting or all allowed languages by default.
sourcestringExact, comma-separated source keys; saved settings or all entitled sources by default.
categorystringComma-separated categories within this key's packs.
regionstringComma-separated regions within this key's packs.
sincestringOptional timestamp filter on held_at, parsed by Python ISO format; timezone-less values assume UTC. RFC 3339 UTC recommended.
fieldsstringComma-separated field whitelist; absent/empty returns every field.
cursorstringPrevious response's next to continue toward older items; omit on first page.
limitintegerDefault 50; 1 to configured maximum (200 by default). Out of range: 400.
Example
curl "https://api.aurith.ca/v1/items?category=business&region=us&lang=en&limit=1&fields=id,headline,source,held_at" \
  -H "Authorization: Bearer $AURITH_KEY"
Item fields

# Item properties

FieldTypeMeaning
idstringItem identifier.
langstringLanguage code.
headlinestringAurith's headline.
bodystringFull article text.
sourcestringSource key.
source_namestringPublisher name used for credit.
categorystring or nullSource category code.
regionstring or nullSource region code.
published_attimestamp or nullPublisher release time, UTC.
held_attimestamp or nullWhen Aurith received the item, UTC.
written_attimestamp or nullWhen Aurith finished writing, UTC.
latency_secondsinteger or nullSeconds between held and published timestamps.
subjectsstring[]Subject tags.
imageobjectOne image per story, the same in every language: url (a PNG or JPEG, no key needed to load it), kind (photo: a US federal government photo, public domain; card: an image Aurith draws for the story, never a fake photo), credit (string or null), alt, width, height.

Timestamps are UTC RFC 3339 values ending in Z. Unknown names in fields return 400 naming that parameter.

Key routes

# Metadata and tools

# GET /v1/key · entitlement

Returns label, tier, per_minute, rate, languages, sources, packs, releases, settings and expires_at.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/key" -H "Authorization: Bearer $AURITH_KEY"

# GET /v1/languages · language codes

Returns languages with code, name and yours.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/languages" -H "Authorization: Bearer $AURITH_KEY"

# GET /v1/sources · source catalog

Returns sources and count; rows contain source, source_name, yours and note.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/sources" -H "Authorization: Bearer $AURITH_KEY"

# GET /v1/status · feed status

Returns items, newest_held_at, seconds_since_newest, languages and asked_at.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/status" -H "Authorization: Bearer $AURITH_KEY"

# GET /v1/portal · billing portal

Returns Stripe url, plan and status; 404 if this key has no Stripe subscription, 502 if Stripe refuses the portal request.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/portal" -H "Authorization: Bearer $AURITH_KEY"
Saved filters

# Key settings

# GET /v1/settings · read defaults

Returns arrays for regions, categories, subjects, sources, languages and releases; unset values are empty arrays.

ParameterDetails
—None.
curl "https://api.aurith.ca/v1/settings" -H "Authorization: Bearer $AURITH_KEY"

# PUT /v1/settings · save defaults

JSON may include regions, categories, subjects, sources, languages and releases as string lists or comma-separated strings. Values are checked against key entitlements; saved defaults fill omitted filters. Empty values clear them. Releases are Basic-only, up to three 24-hour HH:MM Toronto times.

ParameterDetails
JSON bodyOptional object with the six filter properties; each may be omitted, null, empty, a list or comma-separated string.
curl -X PUT "https://api.aurith.ca/v1/settings" \
  -H "Authorization: Bearer $AURITH_KEY" -H "Content-Type: application/json" \
  -d '{"regions":["us"],"categories":["business"]}'
Categories and regions

# Codes used by filters and packs

CategoryNameRegionName
businessBusiness & marketsusUnited States
politicsPolitics & governmentcaCanada
crimeCrime & courtsuk_euUK & Europe
techTechinIndia
healthHealthapacAsia-Pacific
weatherWeather & disasters

Basic items are held until the latest configured release at or before the current time in Toronto. Default release times are 09:00, 14:30 and 19:30; GET /v1/key reports the times for your key.

Public endpoint

# GET /v1/sample

No authentication. Category has default empty string (so supply a valid category), max length 40; valid codes: business, politics, crime, tech, health, weather. Limit defaults to 10 and is 1–10. Returns English items at least three hours old, each with headline, summary (up to 400 characters), source_name, written_at and region. Top-level fields: category, items, held and refreshed_at. Cached for up to five minutes per category; 30 requests/minute per visitor IP. 400 means unknown category; 422 indicates type/length/bound validation. 429 includes Retry-After.

ParameterType / defaultBounds and meaning
categorystring · ""Maximum 40 characters; must be a published category.
limitinteger · 10Minimum 1, maximum 10.

Import the OpenAPI 3.1 specification into Postman, Insomnia or a code generator. It documents public key routes and sample, not account/payment routes.

curl "https://api.aurith.ca/v1/sample?category=business"
Limits and errors

# Rates and error responses

Default rates are Basic 10, Starter 30, Full 120 and basket 60 requests/minute; owner keys have no limit. Account keys share their account's limit. Every 429 includes Retry-After in seconds.

StatusMeaning
400Invalid filter/setting; details name the field.
401Missing or non-current API key/account token.
402Account feed, PUT settings or key creation requires an active subscription.
404Billing unavailable or no Stripe subscription for portal.
409Account already holds three keys.
422Query type validation, such as a non-integer limit.
429Rate limit reached; wait the Retry-After seconds.
502Stripe refused a portal-link request.
503Account identity service unavailable.
Account routes

# Website and app

Use the signed-in Supabase session as a bearer token, not an API key. Availability depends on account and billing configuration.

Method + pathPurpose and parametersExample
GET /v1/account/meAccount, entitlement, alerts, settings and keys; none.GET /v1/account/me
GET /v1/account/feedAccount feed; same filters as /v1/items.GET /v1/account/feed?limit=1
GET, PUT /v1/account/settingsRead/save defaults; PUT accepts the six key settings fields.PUT /v1/account/settings
POST /v1/account/keysJSON label; up to three keys, secret shown once.{"label":"server"}
DELETE /v1/account/keys/{ident}Path ident; revoke an account-owned key.DELETE /v1/account/keys/8f2a1c4d
GET /v1/account/portalStripe billing portal link; none.GET /v1/account/portal
POST /v1/account/checkoutQuery or JSON body: plan (max 40), currency (32), packs (1200), langs (40), interval (max 10, default month), alerts (default false); string query defaults are empty and body values win.{"plan":"starter"}
POST /v1/account/basketJSON add, remove, langs lists/CSV.{"add":["business:us"]}
POST /v1/account/alertsBoolean body field on; true enables alerts now (charged), false removes them at renewal.{"on":true}
GET, PUT /v1/account/alert-timesGET defaults to America/Toronto, 00:00–00:00, all weekdays, missed later. PUT requires tz (IANA), from/to (24h HH:MM), nonempty unique days 0–6, missed later/skip; no extra fields.{"tz":"America/Toronto","from":"00:00","to":"00:00","days":[0,1,2,3,4,5,6],"missed":"later"}
POST, DELETE /v1/account/devicesJSON token 1–4096 chars; POST also platform android/ios and lang 1–32 chars. Up to 10/account.{"token":"device-token","platform":"ios","lang":"en"}
POST /v1/account/refundOptional reason, trimmed to 300 characters.{"reason":"Reason"}
POST /v1/account/undoUndo eligible plan change; none.POST /v1/account/undo

Without an account, the older pay-then-claim flow still works: GET /v1/checkout?plan=<plan> redirects (303) to Stripe Checkout, and GET /v1/claim?session=<id> shows the one-time key after payment.