Authentication
Every request carries an API key. Some keys additionally require a request signature. Both are configured per key in the MineTech portal under Developers → API Keys.
API keys
Authorization: Bearer mt_live_a1b2c3d4e5f6g7h8i9j0k1l2
| Prefix | Environment | Base URL |
|---|---|---|
mt_live_… | Production | https://api.minetech.rw |
mt_test_… | Sandbox | https://sandbox.api.minetech.rw |
The key carries its own tenant, so there is no tenant header to send. The SDK infers the base URL from the prefix — you cannot accidentally point a live key at sandbox.
What a key is, and is not
A key cannot call the authentication endpoints (/v1/auth/*) or manage other
keys. Those are reserved for interactive sessions, so a leaked key cannot escalate
itself into a user login or mint further credentials.
A key can be scoped, expired, IP-restricted and rate-limited. Prefer several narrow keys over one broad one: it makes rotation cheap and blast radius small.
Storage
Keys are stored as a bcrypt hash. MineTech cannot show you a key again after creation and cannot recover one for you — rotation is the only path. Treat the signing secret the same way.
Request signing
Live keys require signing by default. It proves the request body was not altered in transit and bounds replay to a five-minute window.
Send two extra headers:
x-mt-timestamp: 1735689600
x-mt-signature: t=1735689600,v1=<hex HMAC-SHA256>
The signed string is:
{timestamp}.{METHOD}.{path-with-query}.{sha256hex(body)}
Concretely, for GET /v1/operations/lots?page=2 with an empty body at
1735689600:
1735689600.GET./v1/operations/lots?page=2.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
HMAC that string with your signing secret, hex-encode it, and send it as v1.
The t= value must equal the x-mt-timestamp header.
The SDK does all of this when you pass signingSecret:
const client = new MineTech({
apiKey: process.env.MINETECH_API_KEY!,
signingSecret: process.env.MINETECH_SIGNING_SECRET!,
});
Full worked examples in several languages: Request signing.
Signatures are valid for 300 seconds either side of server time. A
timestamp_stale error is a clock problem, not a secret problem — check NTP on the
calling host before regenerating anything.
IP allowlists
Optionally restrict a key to specific addresses or CIDR blocks under Developers → IP Allowlist, per environment.
An empty allowlist means requests are accepted from any address. It is not a partially-configured state — if you add no entries, no IP restriction applies. Add at least one entry for the restriction to take effect.
Both IPv4 and IPv6 are supported, including IPv4-mapped IPv6 (::ffff:10.0.0.1
matches 10.0.0.0/8), so enabling IPv6 at your edge will not silently break an
allowlist.
Errors
| Status | Code | Meaning | Fix |
|---|---|---|---|
| 401 | api_key_missing | No Authorization header | Send Bearer <key> |
| 401 | api_key_invalid | Unrecognised key | Usually a truncated copy |
| 401 | api_key_revoked | Key was revoked | Issue a new key |
| 401 | api_key_expired | Past its expiry | Rotate the key |
| 401 | signature_missing | Key requires signing | Send both signing headers |
| 401 | timestamp_stale | Outside the 300s window | Fix the host clock |
| 401 | signature_mismatch | Digest did not match | Check the canonical string |
| 403 | insufficient_scope | Key lacks the scope | Grant it in the portal |
| 403 | ip_not_allowed | Source address not allowlisted | Add the address |
| 429 | rate_limit_exceeded | Per-key limit exceeded | Honour Retry-After |
Each signing failure is reported distinctly on purpose — "your clock is skewed" and
"your secret is wrong" need different fixes, and collapsing both into a generic
401 makes integration debugging guesswork.
Rotation
Rotating issues the replacement before revoking the old key, so there is never a window with no working credential. The old key stops working as soon as rotation completes, so deploy the new one first.
Rotation mints a new signing secret as well. Update both.