Key formats and secret scanning
Every NorReach credential has a fixed shape and a checksum, so a scanner can find one in code, logs or chat with almost no false alarms, and confirm it without asking NorReach. Add the patterns below to the secret scanning you already run — pre-commit hooks, CI, your repository host's custom patterns.
The formats
| Credential | Pattern | Notes |
|---|---|---|
| NorReach API key | \bnr_live_[0-9A-Za-z]{43}_[0-9A-Za-z]{6}\b | Shown once when it is created; works until it is revoked or expires. |
| NorReach OAuth access token | \bnr_oat_[0-9A-Za-z]{43}_[0-9A-Za-z]{6}\b | Issued to a connected app; 1 hour. |
| NorReach OAuth refresh token | \bnr_ort_[0-9A-Za-z]{43}_[0-9A-Za-z]{6}\b | Issued to a connected app; replaced on every use; 60 days idle. |
- The prefix, then 43 characters (32 random bytes, base62), an underscore and a 6-character checksum. No other characters, and no letter, digit or underscore directly before or after.
- The patterns are case-sensitive. Old-style keys (nrk_ and 32 lowercase hex characters) no longer work with the API; scan for them too:
\bnrk_[0-9a-f]{32}\b
The checksum
- Take the 43 characters between the prefix and the last underscore (the body).
- Compute the CRC-32 of the body's bytes — the IEEE 802.3 polynomial, as in zlib and PNG (reflected 0xEDB88320, initial value 0xFFFFFFFF, final XOR 0xFFFFFFFF) — as an unsigned 32-bit number.
- Write that number in base62 with the alphabet
0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz(0 is "0", 61 is "z"), most significant digit first, padded on the left with "0" to 6 characters. - It must equal the 6 characters after the last underscore. The body is the 32 random bytes as one big-endian number in the same base62, padded to 43 — there is nothing more to check in it.
TypeScript
const B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const SHAPE = /^nr_(live|oat|ort)_([0-9A-Za-z]{43})_([0-9A-Za-z]{6})$/;
// CRC-32 (IEEE 802.3, the zlib / PNG one) of some bytes, as an unsigned integer.
function crc32(bytes) {
let c = 0xffffffff;
for (const b of bytes) {
c ^= b;
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
}
return (c ^ 0xffffffff) >>> 0;
}
// True when the string has the shape of a NorReach credential AND its checksum is right.
export function isNorReachCredential(s) {
const m = SHAPE.exec(s);
if (!m) return false;
let n = crc32(new TextEncoder().encode(m[2]));
let check = "";
for (let i = 0; i < 6; i++) {
check = B62[n % 62] + check;
n = Math.floor(n / 62);
}
return check === m[3];
}Python
import re, zlib
B62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
SHAPE = re.compile(r"nr_(live|oat|ort)_([0-9A-Za-z]{43})_([0-9A-Za-z]{6})")
def is_norreach_credential(s: str) -> bool:
m = SHAPE.fullmatch(s)
if not m:
return False
n = zlib.crc32(m.group(2).encode()) & 0xFFFFFFFF
check = ""
for _ in range(6):
check = B62[n % 62] + check
n //= 62
return check == m.group(3)If a credential leaks
- An API key: revoke it in Settings › API & webhooks (or rotate it with no grace) and put the new one where the old one was. Revoking is effective on the next request.
- A connected app's token: disconnect the app in Settings › API & webhooks › Connected apps; every token of it stops. A refresh token used twice also ends the connection by itself.
- Then read the audit log for what the credential did: every write names the key or app it came through.
Unusual activity
NorReach watches every key and connected app for activity far beyond its own normal: every 5 minutes, the last 5 are compared with the busiest 5 % of its 5-minute periods over the past 7 days. Owners and admins get a notification when a key makes more than 3× its usual requests (at least 300 in 5 minutes), more than 3× its usual sends, calls, publishes, approvals or exports (at least 20), or a burst of failed requests (at least 100) — at most once an hour per key.
At 10× its usual — and at least 100 sends, calls, publishes, approvals or exports, or 3000 requests, in 5 minutes — the key or app is suspended at once: every request answers 403 key_suspended until an owner or admin resumes it in Settings › API & webhooks, or revokes it. A new key has no history yet and is judged by those minimums alone. If your integration really works in big bursts, spread them out, or ask us to adjust the check for your workspace.