Start here
Authentication and abilities
One header, everywhere: a bearer token that belongs to the team and carries exactly the abilities somebody chose to grant it. Nothing on this surface is public.
The credential
Send the token on every request:
Authorization: Bearer wt_9f3kd02m_…
Tokens are issued by a team owner in Settings → API tokens and belong to
the team, not to a person: an integration keeps working when whoever set it
up moves on. The secret starts with wt_, is shown exactly once at
issue, and is stored only as a hash; there is no path that shows it again. Give each
integration its own token with the fewest abilities that do the job, and revoke a token the
moment its integration is retired. Revocation is recorded rather than erased, so a key seen
in a log later is still identifiable.
Three refusals, kept apart
Authentication can fail three different ways, and each answers differently so your handling can too:
- 401
unauthenticated: the token is missing, unknown, expired or revoked. Rotate the credential. - 402
plan_limit: the token is fine, but the plan does not include API access. The message names the tier that would. - 403
missing_ability: the token is fine and the plan allows it, but this key was not granted the ability the endpoint requires.required_abilitynames it.
A 403 is never a tenancy answer. Another team's record is a 404, exactly as if it did not exist, because a distinguishable refusal would confirm the id.
Abilities
Every endpoint declares the ability it requires; the reference states it beside each one, and
GET /api/v1 lists every endpoint with whether your token may call it. Reads and
writes are split throughout, and PDF export is its own grant: a read-only key should not be
able to walk every report a team owns and take a copy of each.
reports:read
Read reports
List reports and read their findings, records and coverage.
reports:write
Create and delete reports
Request new reports, and delete existing ones.
reports:export
Download report PDFs
Download the rendered PDF of a report.
maps:read
Read map tiles
Fetch vector map tiles of the weather archive. Not scoped to a team: the archive is public federal record, and a tile is the same answer for everyone.
properties:read
Read properties
List the team's saved properties, their contacts, and the storm days on record near each.
properties:write
Create and change properties
Save, change and remove properties, and run a report about one.
territories:read
Read territories
List the team's territories with their shapes as GeoJSON, and the storm days inside each.
territories:write
Create and change territories
Draw, change and remove territories, including submitting a shape as GeoJSON.
exposures:read
Read activity
Read every storm day and warning that touched something the team watches.
exposures:write
Acknowledge activity
Mark activity as seen, or set it aside.
account:read
Read the account ledger
Read the credit balance and its movements, the team's plan and what it holds against its limits, and when monitoring last ran.
integrations:read
Read integrations
List where the team's systems are told about storms, and whether the posts arrived.
integrations:write
Create and change integrations
Register, change and remove those destinations, post a test to one, and read or rotate its signing secret. The secret verifies every post we make, so this is authority over what a receiver will believe.
Prove it works
The account endpoint is the mirror: it answers with the token you presented, the plan behind it, and what is left to spend. It requires no ability at all, so it is the right first call for a health check.
Retrieve the account
GET /api/v1/account
The credential, the plan, and the limits, before anything is spent.
any valid token
Answers
200
The answer.
Plus the shared refusals: 401, 402, 403, 404, 422 and 429, described on Errors.
curl https://titanweather.com/api/v1/account \
-H "Authorization: Bearer $TITAN_API_TOKEN"
$titan = new \GuzzleHttp\Client([
'base_uri' => 'https://titanweather.com',
'headers' => ['Authorization' => 'Bearer '.getenv('TITAN_API_TOKEN')],
]);
$response = $titan->get('/api/v1/account');
$account = json_decode((string) $response->getBody(), true);
const response = await fetch('https://titanweather.com/api/v1/account', {
headers: {
Authorization: `Bearer ${process.env.TITAN_API_TOKEN}`,
},
});
const account = await response.json();
import os
import requests
response = requests.get(
"https://titanweather.com/api/v1/account",
headers={
"Authorization": f"Bearer {os.environ['TITAN_API_TOKEN']}",
},
)
account = response.json()
{
"object": "account",
"team": {
"name": "North Texas Claims Group",
"plan": "professional",
"plan_label": "Professional",
"on_trial": false,
"lapsed": false
},
"limits": {
"lookback_years": 10,
"max_radius_miles": 10,
"monthly_reports": 25,
"reports_used": 7,
"reports_remaining": 18,
"pdf_export": true
},
"credits": {
"balance": 18,
"monthly_allowance": 25,
"used_this_period": 7,
"renews_at": "2026-09-01T00:00:00Z"
},
"token": {
"name": "Claims intake",
"prefix": "wt_9f3kd02m",
"abilities": [
"reports:read",
"reports:write",
"reports:export"
],
"expires_at": null,
"last_used_at": "2026-08-23T14:02:11Z"
}
}