Authentication#
API Keys#
HashWatch uses API keys for authentication on all private endpoints. An API key looks like this:
<team-id>.<secret>For example: acme-ir.sk_live_a1b2c3d4e5f6...
Sending your API key#
Include your key in the X-API-Key header on every request:
curl https://api.hashwatch.us/api/v1/intel/stats \
-H "X-API-Key: acme-ir.sk_live_a1b2c3d4e5f6..."API keys are never logged or stored in plaintext - only a secure HMAC hash is kept on the server. Keep your key confidential; it cannot be recovered if lost (request a new one from your administrator).
Web console sign-in#
You do not have to use the API directly. The same key signs you in to the HashWatch web console at:
https://hashwatch.us/login(There is also a Sign in link in the top-right of the public dashboard.) Any valid key works - what you can do once inside depends on your role:
- Account Administrators can issue, update, and revoke API keys for their own team, and grant additional or time-limited roles.
- Analyst / Responder / Auditor roles can view the dashboard and manage their own sign-in security (passkey, TOTP, recovery codes) on the Account page.
After your first sign-in with the key, enroll a passkey or authenticator (TOTP) so you can sign in without pasting the key each time. The navigation bar shows only the sections your role can use.
Multi-factor authentication is required in the web console. On first sign-in you are taken to the Account page and must enroll a passkey or authenticator before using anything else.
- API-key sessions (a raw
<team>.<secret>key, used by scripts and SIEM integrations) are unaffected - MFA is a console convenience there and programmatic access keeps working without it. - Invited-user logins (you redeemed a one-time invite code instead of pasting a
key) must have MFA enrolled to do anything beyond enrolling it: the
requirement is enforced at the API too, so an unenrolled user session is rejected
with
403 MFA_REQUIREDon intel and admin routes (only the MFA-enrollment and account-switch endpoints stay reachable). You cannot skip it by calling the API directly.
Reissue your key when you’re done with it. Once you’ve signed in and set up MFA, the Account page can reissue your API key (the old one is revoked immediately and a new one is shown once) - useful if you only needed the key to sign in or are concerned about keeping a copy.
JWT Tokens (optional)#
For high-throughput use cases, you can exchange your API key for a short-lived JWT token (15-minute TTL) and use that for subsequent requests. This avoids the HMAC verification overhead on every call.
Obtain a token#
curl -X POST https://api.hashwatch.us/api/v1/auth/token \
-H "X-API-Key: acme-ir.sk_live_a1b2c3d4e5f6..." \
-H "Content-Type: application/json"Response:
{
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900
}expires_in is the token lifetime in seconds (900 = 15 minutes). The signed JWT
carries these claims: sub (your team), role (your primary role), roles (your
full effective role set - the primary role plus any additional or time-limited
grants), and tier. Permissions are the union across every role in roles.
Use the token#
curl https://api.hashwatch.us/api/v1/intel/stats \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."Refresh before expiry#
curl -X POST https://api.hashwatch.us/api/v1/auth/refresh \
-H "Authorization: Bearer <current-token>"The public key used to verify JWT signatures is available at:
GET https://api.hashwatch.us/.well-known/jwks.jsonRoles and Tiers#
Every API key has an assigned role (what actions are allowed) and tier (billing gate for paid features). Both must be satisfied for a request to succeed.
What you can do with an API key#
An API key authenticates you to the private API and carries your role + tier. Depending on those, a key lets you:
- Look up file hashes against the BinTrust corpus of known-good vendor releases and NSRL (
intel:lookup). - View platform statistics (
intel:stats). - Query historical hashes - what the official hash for a piece of software was on any past date (
intel:history, basic). - Pull the download audit trail - provenance proof that vendor files were verified and never stored (
intel:downloads, teams). - Consume the RevokeRadar feed - certificates that signed tracked software and have since been revoked by the CA, plus a queryable revocation history / timeline showing when each cert was revoked (
intel:revocations, teams). - Administer access - issue, update, and revoke keys, with the scope determined by your administrative role (see Administrative roles below).
You can also exchange a key for a short-lived JWT (see JWT Tokens). Keys are HMAC-hashed at rest and shown only once at creation.
Permissions#
| Permission | Endpoint | Minimum Tier |
|---|---|---|
intel:stats | GET /api/v1/intel/stats | basic |
intel:lookup | POST /api/v1/intel/lookup | basic |
intel:history | GET /api/v1/intel/history | basic |
intel:downloads | GET /api/v1/intel/downloads | teams |
intel:revocations | GET /api/v1/intel/revocations (RevokeRadar feed) | teams |
intel:revocations | GET /api/v1/intel/revocations/history (RevokeRadar timeline) | teams |
intel:revocations | GET /api/v1/intel/revocations/{thumbprint} (per-cert detail) | teams |
tenant:manage | /api/v1/admin/keys/* - your own tenant only | teams |
tenant:impersonate | POST /api/v1/admin/impersonate - view-as / manage-as a tenant | enterprise |
platform:keys | /api/v1/admin/keys/* - all tenants | enterprise |
platform:settings | platform settings, vendors, audit, RBAC, health | enterprise |
platform:admins | manage platform administrators | enterprise |
Customer (account) roles#
There are three customer roles, assignable only within your own account - never across accounts or over the platform. The internal role key is shown below alongside its default display label (deployments can rename labels for white-labeling, so what you see in your console may differ):
| Role (internal key) | Default label | Hash lookup & stats | History & downloads & RevokeRadar | Manage your account’s keys |
|---|---|---|---|---|
analyst | Viewer | ✓ | ||
auditor | Analyst | ✓ | ✓ | |
account_admin | Account Admin | ✓ | ✓ | ✓ (own account only) |
Tier gates (the role must grant the permission and the account’s tier must meet the floor):
intel:historyrequires basic tier;intel:downloadsand the RevokeRadarintel:revocations(feed, history timeline, and per-cert detail) require teams tier (formerlypaid, renamed 2026-06-17;paidstill accepted as a deprecated alias). The earlierresponderandsenior responderroles were removed (they duplicatedanalyst/auditor).
account_adminis account-scoped. It can issue, update, and revoke API keys only inside its own account, and may add members or issue keys only with theanalyst/auditorroles. It can never see or affect other accounts, change platform settings, grant a platform role, or create anotheraccount_admin- promoting someone to Account Admin requires HashWatch staff.
Administrative roles (HashWatch only)#
Platform roles maintain the HashWatch product itself and are never assignable to a customer tenant, regardless of subscription.
| Role | Scope |
|---|---|
client_liaison | HashWatch client-facing staff: full intel access plus the ability to view-as / manage-as a single customer tenant for demos and support (tenant:impersonate). Has no platform settings, platform-wide key management, or admin power. Usually granted as an additional role on top of another role. |
platform_admin | HashWatch staff: manage keys across all tenants, plus platform settings, vendors, and audit. Also holds tenant:impersonate. Cannot manage other platform admins or the owner. |
platform_owner | Protected root: everything platform_admin can do, plus managing platform admins. Cannot be revoked, demoted, or have its permissions changed by anyone else. |
The permission sets of all administrative roles (including client_liaison) are fixed by the platform and cannot be edited in the RBAC editor - so no administrator can strip another’s access or lock out the owner.
Multiple roles and time-limited grants#
A key has one primary role (its identity and tier gate) and may also carry additional roles granted on top of it. Each additional grant can be permanent or time-limited (auto-expiring, capped at 30 days) - useful for demos and short-lived elevation during training.
- A key’s effective permissions are the union of its primary role and every currently-active grant. Expired grants simply stop counting; no cleanup is needed.
- The JWT
rolesclaim lists the full effective set;roleremains the primary. - Granting a role still respects authority: platform roles remain owner-only, and a caller can only grant roles at or below its own rank.
Managed via POST /api/v1/admin/keys/{keyID}/roles (grant) and
DELETE /api/v1/admin/keys/{keyID}/roles/{role} (revoke).
Tenant impersonation (view-as / manage-as)#
Staff holding tenant:impersonate (client_liaison, platform_admin,
platform_owner) can step into a customer context, in one of two scopes:
# Tenant scope: step into a tenant as a generic account_admin.
curl -X POST https://api.hashwatch.us/api/v1/admin/impersonate \
-H "Authorization: Bearer <staff-jwt>" \
-H "Content-Type: application/json" \
-d '{"team_id": "acme-ir", "mode": "manage"}'
# User scope: step into a specific user, assuming THEIR membership role.
curl -X POST https://api.hashwatch.us/api/v1/admin/impersonate \
-H "Authorization: Bearer <staff-jwt>" \
-H "Content-Type: application/json" \
-d '{"user_id": "<uuid>", "account_id": "<uuid>", "mode": "view"}'Modes: view (read-only - every write method is rejected with 403) or
manage (read-write). Response is a short-lived (30-minute) JWT carrying act
(the staffer, recorded as the audit actor) and imp_mode claims.
- Tenant scope mints the session as
account_adminon that tenant. - User scope (provide
user_id;account_idonly needed if the user belongs to more than one account) assumes the user’s actual membership role in that account- so “manage as” an Analyst is limited to what that Analyst can do - and records
the impersonated user in an
imp_uidclaim. Staff users and suspended accounts are rejected.
- so “manage as” an Analyst is limited to what that Analyst can do - and records
the impersonated user in an
The assumed role is always a tenant/customer role, so the session has no platform reach and impersonation cannot be chained. Impersonation sessions cannot mint or refresh tokens (no escalation), and every action taken under one is attributed to the staffer in the audit log. Impersonating your own team, an unknown tenant/user, or a target that holds platform roles is rejected.
Tiers#
| Tier | What it unlocks |
|---|---|
free | Public dashboard only - no API key required |
basic | Entry API tier: intel:stats, intel:lookup (BinTrust hash lookup), and intel:history (historical hash-of-day lookup) |
teams | intel:downloads, intel:revocations (RevokeRadar feed + history timeline + per-cert detail), and account_admin tenant administration (when the role grants them). Formerly paid (renamed 2026-06-17; paid still accepted as a deprecated alias). |
enterprise | Full customer feature access; platform administration (platform_*) is HashWatch-internal |
Error responses#
All errors return JSON with an error field:
{"error": "unauthorized"}
{"error": "forbidden"}
{"error": "rate limit exceeded"}| HTTP Status | Meaning |
|---|---|
401 Unauthorized | Missing or invalid API key / JWT |
403 Forbidden | Valid credential but insufficient role or tier |
429 Too Many Requests | Rate limit exceeded - back off and retry |
Rate limits#
| Endpoint group | Limit |
|---|---|
GET /public/hash-of-day | 100 requests / minute / IP |
POST /api/v1/auth/token | 10 requests / minute / key |
| All other authenticated endpoints | 60 requests / minute / key |
Rate limits are enforced at the Cloudflare edge before requests reach the application. Exceeding the limit returns HTTP 429. The Retry-After header (seconds) indicates when you may retry.
Admin authentication endpoints#
These endpoints are used by the Admin Panel web UI and by automation that manages admin sessions. They are separate from the team API key flow above - they authenticate platform administrators, not data consumers.
Token endpoints (current)#
| Method | Path | Description |
|---|---|---|
POST | /api/v1/auth/token | Exchange an API key for a short-lived JWT (15 min) |
POST | /api/v1/auth/refresh | Renew a JWT before expiry (returns a new 15-min token) |
GET | /.well-known/jwks.json | RSA public key for verifying JWT signatures |
See JWT Tokens above for request/response details.
Passkey (WebAuthn) endpoints#
Passkeys use the Web Authentication API (WebAuthn). Registration and authentication each require two calls: one to get a challenge from the server, and one to return the signed response from the browser/device.
Register a new passkey#
POST /api/v1/admin/mfa/passkey/beginAuth required: valid admin JWT or API key
Request body: {"display_name": "MacBook Touch ID"}
Response: WebAuthn PublicKeyCredentialCreationOptions challenge object (pass to navigator.credentials.create())
POST /api/v1/admin/mfa/passkey/completeAuth required: same session as begin
Request body: the PublicKeyCredential object returned by the browser
Response: {"passkey_id": "...", "display_name": "MacBook Touch ID", "created_at": "..."}
Sign in with a passkey#
POST /api/v1/admin/auth/passkey/beginAuth required: none
Request body: {} (or {"user_handle": "..."} to hint a specific account)
Response: WebAuthn PublicKeyCredentialRequestOptions challenge (pass to navigator.credentials.get())
POST /api/v1/admin/auth/passkey/completeAuth required: none
Request body: the PublicKeyCredential assertion returned by the browser
Response: {"token": "eyJ...", "expires_at": "..."} - same JWT format as POST /api/v1/auth/token
List and remove passkeys#
GET /api/v1/admin/mfa/passkeys # list registered passkeys for the caller
DELETE /api/v1/admin/mfa/passkeys/{id} # remove a passkey by IDBoth require a valid admin JWT. The caller’s overall MFA status is available at
GET /api/v1/admin/mfa/status.
TOTP endpoints#
Enroll TOTP#
POST /api/v1/admin/mfa/totp/enrollAuth required: valid admin JWT
Request body: {}
Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"otpauth_url": "otpauth://totp/HashWatch%3Aplatform-admin?secret=JBSWY3D...&issuer=HashWatch",
"qr_code_svg": "<svg>...</svg>"
}The otpauth_url can be scanned as a QR code or entered manually into any authenticator app.
Confirm TOTP enrollment#
After scanning the QR code, verify the first code to activate TOTP:
POST /api/v1/admin/mfa/totp/verifyAuth required: valid admin JWT
Request body: {"code": "123456"}
Response: {"enrolled": true} - TOTP is now active for this account
Disable TOTP#
DELETE /api/v1/admin/mfa/totpAuth required: valid admin JWT
Request body: {"code": "123456"} - must provide a valid current code to confirm
Response: {"disabled": true}
Recovery code endpoints#
Generate recovery codes#
POST /api/v1/admin/mfa/recovery/generateAuth required: valid admin JWT
Request body: {}
Response:
{
"codes": [
"a3b7-f2d9-1e4c",
"9x2k-0m5p-7h3q",
"..."
],
"count": 10,
"warning": "Store these offline. Each code can only be used once."
}Calling this endpoint invalidates any previously generated codes for the account.
Sign in with a recovery code#
POST /api/v1/admin/auth/recoveryAuth required: none
Request body: {"code": "a3b7-f2d9-1e4c"}
Response: {"token": "eyJ...", "expires_at": "..."} - the used code is invalidated immediately
Sign-in with TOTP uses
POST /api/v1/admin/auth/totpwith{"code": "123456"}, returning the same{"token", "expires_at"}shape.
Remaining code count is returned in the
X-Recovery-Codes-Remainingresponse header so the admin panel can warn when codes are running low.