Authentication

API keys, live versus test, and how to rotate safely.

Every request needs a key in the Authorization header.

curl https://api.cruxal.in/v1/filings \
  -H "Authorization: Bearer ck_live_..."

Keys are server-side credentials

There is no CORS policy that allows browser calls, and that is deliberate. A key embedded in front-end JavaScript or in a mobile app binary is a published key — anyone who opens developer tools has it. Call this API from your own backend and proxy what your users need.

For the same reason, the key is never accepted in a query string. Query strings are written to server access logs, load-balancer logs and browser history. Header only.

Live and test keys

A test key is free, needs no plan, and you can create one the moment you sign up.

ck_live_ck_test_
DataReal, currentReal, delayed 60 minutes
HistoryFullLast 7 days
QuotaYour plan's1,000 requests/month
Rate limitYour plan's10 requests/minute
ExpiresNever — revoke when done30 days after creation

Test keys are not a sandbox with fake data. They return the same shapes from the same tables, so anything you build against one runs unchanged against a live key. What differs is how much of each record you get:

Fieldck_test_ returns
headline, ticker, isin, company_name, filed_at, categoryIn full
urlIn full
summaryFirst ~120 characters
sentimentIn full
impact_bandIn full — low / medium / high
impact_scorenull (the 0-10 score is withheld)
tagsnull
price_contextnull
logo_urlnull
expected_direction, conviction on /v1/eventsnull

Note that no field changes type. Withholding nulls a value; it never swaps a number for a string, because that is precisely what would break your client on upgrade. impact_band is present on every plan, so impact filtering works the same everywhere — what a paid plan adds is the score to rank on.

That is enough to write and test a real integration — every field a client parses is present, so nothing about the shape changes when you upgrade. It is not enough to run a product on, and it is not meant to be.

The environment is a property of the key recorded when it is created. It is not inferred from the prefix you send.

If a paid plan lapses, its live key is not disabled — it drops to these evaluation limits and this payload, and keeps working. Your integration degrades rather than breaking, and /v1/usage reports entitled: false so you can see why.

Storage and rotation

We store a SHA-256 hash of your key, never the key itself. The plaintext is shown exactly once, on the screen where you create it. It cannot be recovered — if you lose it, revoke it and create a new one.

To rotate without downtime:

  1. Create a second key.
  2. Deploy it to your service.
  3. Confirm traffic has moved (the Last used column on API Keys updates within a minute).
  4. Revoke the old key.

Revocation takes effect immediately: the very next request with that key gets a 401.

What a failure looks like

Missing, malformed, unknown, revoked and expired keys all return the same response:

{
  "type": "https://www.cruxal.in/docs/errors#unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Invalid or expired API key",
  "instance": "/v1/filings"
}

They are deliberately indistinguishable. Telling you that a key exists but is revoked would also tell an attacker that their guess named a real key.

Get started

Create a free account, then mint an API key and make your first request in under two minutes.