@minetech/node
v0.1.1
Published
Official MineTech Node.js SDK — mining operations, workforce, safety, compliance and reporting.
Readme
@minetech/node
Official Node.js SDK for the MineTech API — mining operations, workforce, safety, compliance, inventory, environment, finance and reporting.
Full reference: docs.minetech.rw
Install
npm install @minetech/nodeRequires Node 18 or newer. Zero runtime dependencies.
Quick start
Issue a key in the MineTech portal under Developers → API Keys. You get two values, shown once: the key itself and a signing secret.
import { MineTech } from '@minetech/node';
const client = new MineTech({
apiKey: process.env.MINETECH_API_KEY!,
// Required when the key enforces request signing — the default for live keys.
signingSecret: process.env.MINETECH_SIGNING_SECRET,
});
const incident = await client.safety.incidents.create({
title: 'Loose ground at face 3',
severity: 'HIGH',
description: 'Spotted during pre-shift inspection.',
});The base URL is inferred from the key prefix: mt_live_… → production,
mt_test_… → sandbox. Override with baseUrl for a local gateway.
Listing and pagination
list() is awaitable for a single page, or iterable to sweep everything without
holding it all in memory:
// One page
const { items, meta } = await client.operations.lots.list({ limit: 50 });
// Every lot, fetched lazily
for await (const lot of client.operations.lots.list({ siteId }).autoPaging()) {
console.log(lot);
}
// Bounded collection
const recent = await client.workforce.workers
.list({ limit: 100 })
.toArray({ maxItems: 500 });Namespaces
Organised by domain, not by internal service. operations is nested a second
level because it is by far the largest surface:
client.operations.lots client.workforce.workers
client.operations.tunnels client.workforce.attendance
client.operations.productionLogs client.workforce.payroll
client.operations.shiftLogs client.safety.incidents
client.operations.custody client.safety.inspections
client.operations.analytics client.compliance.licenses
client.inventory.stock
client.environment.monitoring
client.finance.invoicesAlso: reports, reportBuilder, dashboards, users, roles, permissions,
tenantConfig, auditLogs, files, sync, notifications, disbursements,
payrollExport, developer.
For anything a namespace does not cover yet:
await client.request('POST', '/operations/lots/abc/split', { body: { … } });
await client.operations.lots.action('POST', 'abc/split', { body: { … } });Errors
Every failure is a typed subclass, so instanceof narrows correctly:
import {
ApiError, AuthenticationError, PermissionError, NotFoundError,
ValidationError, RateLimitError, ServerError,
TimeoutError, ConnectionError,
} from '@minetech/node/errors';
try {
await client.safety.incidents.create({ … });
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.fieldErrors);
} else if (error instanceof RateLimitError) {
console.error(`Retry in ${error.retryAfterSeconds}s`);
} else if (error instanceof ApiError) {
console.error(error.status, error.code, error.requestId);
}
}Quote error.requestId in any support conversation — it identifies the exact
request server-side.
Retries and idempotency
Network errors, timeouts, 408, 429 and 5xx are retried automatically with
exponential backoff and jitter, honouring Retry-After when the server sends it.
4xx responses are not retried — they would fail identically.
Every write carries an Idempotency-Key, generated once and reused across
retries, so a retried POST cannot create a duplicate. Supply your own to
dedupe across processes:
await client.finance.invoices.create(payload, { idempotencyKey: `invoice-${jobId}` });Tune or disable:
new MineTech({ apiKey, maxRetries: 0, timeoutMs: 10_000, maxRetryDelayMs: 5_000 });Request signing
When a key enforces signing, pass signingSecret and the SDK handles it — each
request (and each retry) gets a fresh x-mt-timestamp and x-mt-signature.
Signatures are valid for 300 seconds, so the server clock and yours must agree;
a timestamp_stale error means checking NTP, not the secret.
Webhooks
Verify from a dedicated entry point that needs no client:
import express from 'express';
import { constructEvent, SignatureVerificationError } from '@minetech/node/webhooks';
const app = express();
// MUST be the raw body. A parsed-and-re-stringified body will not match the
// signature — key order and number formatting both drift.
app.post('/webhooks/minetech', express.raw({ type: 'application/json' }), async (req, res) => {
try {
const event = await constructEvent({
payload: req.body,
signatureHeader: req.header('x-mt-signature'),
secret: process.env.MINETECH_WEBHOOK_SECRET!,
});
switch (event.type) {
case 'safety.incident.reported':
console.log(event.data.title);
break;
default:
// Unknown types parse fine — new events never break a deployed receiver.
console.log('unhandled', event.type);
}
// Acknowledge fast; do the work asynchronously. Deliveries time out at 10s
// and are retried on failure.
res.sendStatus(200);
} catch (error) {
if (error instanceof SignatureVerificationError) {
console.error(error.reason);
return res.sendStatus(400);
}
throw error;
}
});Failed deliveries retry at 0 → 1m → 5m → 30m → 2h → 12h → 24h, then stop and
alert. event.id is stable across retries — use it to dedupe.
Observability
new MineTech({
apiKey,
onRequest: ({ method, path, attempt }) => log.debug({ method, path, attempt }),
onResponse: ({ status, durationMs }) => metrics.timing('minetech', durationMs, { status }),
onRetry: ({ attempt, delayMs, error }) => log.warn({ attempt, delayMs, error }),
});Response types
Most methods currently return unknown. This is deliberate: the MineTech API does
not yet publish response schemas for every endpoint, and asserting shapes the API
does not guarantee would be worse than admitting the gap. Narrow at the call site:
const lot = (await client.operations.lots.get(id)) as { id: string; grade: number };Coverage is tracked in
sdk-service/specs/coverage-report.json and
improves with each release as response schemas land upstream — no SDK change
needed on your side.
License
Proprietary. © MineTech.
