Integration API and webhooks · v1

Read data. Receive signed events.

Build organisation-scoped integrations for equipment, calibration planning, completed work, certificates and quality cases. Use the read-only API for current state and signed webhooks to learn when important records change.

Quick start

Make your first request

Create a key under Integrations → API keys in your organisation. Keep the secret in a server-side secret manager and send it only in the bearer authorisation header.

Base path

/v1/integrations/organisations/{organisation_id}

Example

curl --fail-with-body \
  --header "Authorization: Bearer $METRA_API_KEY" \
  --header "Accept: application/json" \
  "https://api.obsidianmetra.com/v1/integrations/organisations/{organisation_id}/equipment?page_size=50"

Responses use JSON, timestamps use ISO 8601 UTC and identifiers are UUIDs. Certificate downloads return the validated file media type instead of JSON.

Authentication

Organisation-bound service credentials

A Metra API key is a service credential permanently bound to one organisation. The organisation_id in the request path must match that key. API keys cannot sign in to the application or call write operations.

Authorization: Bearer metra_live_<credential-id>_<secret>
The complete key is shown once. Do not put it in a URL, browser bundle, spreadsheet, source file, support request or log. Rotate a key if its secret may have been exposed.

Least privilege

Scopes

Select only the fixed read capabilities the integration needs.

ScopeAccess
integration:equipment:readEquipment identity, category, status and current deployment
integration:structure:readClients, sites and locations
integration:calibration:readRequirements, due status, dashboard totals and calibration events
integration:certificates:readCertificate metadata and private downloads
integration:quality:readOut-of-tolerance case identity and workflow state

Certificate routes require both integration:calibration:read and integration:certificates:read.

Reference

Endpoints

Append each path below to the organisation base path. Every endpoint uses GET.

Equipment and structure

equipment:read / structure:read
  • GET /equipment List equipment and its current deployment
  • GET /equipment/{equipment_id} Read one equipment record
  • GET /clients List clients
  • GET /sites List sites
  • GET /locations List hierarchical locations

Calibration planning

calibration:read
  • GET /calibration-requirements List requirements and authoritative due state
  • GET /calibration-dashboard/summary Read aggregate calibration status counts

Events and certificates

calibration:read / certificates:read
  • GET /calibration-events List retained calibration events
  • GET /calibration-events/{event_id} Read one calibration event
  • GET /calibration-events/{event_id}/certificates List certificate metadata
  • GET /calibration-events/{event_id}/certificates/{certificate_id}/download Download an authorised private certificate

Quality cases

quality:read
  • GET /out-of-tolerance-cases List case identity and workflow state
  • GET /out-of-tolerance-cases/{case_id} Read one quality case

Reliable synchronisation

Pagination, filters and checkpoints

List routes use deterministic, opaque cursor pagination. The default page size is 50 and the maximum is 100. Follow next_cursor exactly; do not construct or modify it.

  • Use changed_since where documented for incremental synchronisation.
  • Do not combine cursor and changed_since.
  • Store the final checkpoint only after committing every page.
  • Run periodic full reconciliation to recover from consumer errors or expired state.
  • Unknown query parameters are rejected instead of silently ignored.

Available filters include equipment status, category and site; requirement due state, equipment and due-date range; event status, result and performed-date range; and quality case status and equipment. Date ranges are limited to 366 days.

Operational behaviour

Errors and request limits

StatusMeaning
400Invalid identifier, cursor, filter or date range
401Missing, malformed, expired or revoked API key
403API key does not have the required scope
404Resource is unavailable within the key's organisation
429Request rate exceeded; retry after the indicated interval
500Unexpected server failure

The initial limits are 120 requests per API key per minute and 600 requests per organisation per minute on each API process. A 429 response includes Retry-After. Back off and retry safely.

Keep the response X-Trace-ID when contacting support, but never send the key, authorisation header, certificate content or sensitive response data.

Signed webhooks

Configure a webhook receiver

Configure webhook endpoints in the application under Integrations → Webhooks. Select the event types your receiver needs and keep the one-time signing secret in a secrets manager.

Destinations must use public HTTPS on port 443. Redirects, embedded credentials, private or reserved networks and cloud metadata destinations are rejected. Metra verifies the destination with a signed challenge before domain events can be delivered, and revalidates DNS before every attempt.

Changing an endpoint URL requires verification again. Normal secret rotation keeps the old secret available for a visible 24-hour overlap; use immediate cutover if compromise is suspected.

Verification sends a signed webhook.endpoint_verification.v1 challenge. After validating it, return 204 No Content with Metra-Webhook-Verification: <event-id>. Domain events are not delivered until this challenge succeeds.

Immutable public contracts

Event catalogue

A webhook identifies a committed occurrence. Use the event's resource ID with a suitably scoped read API key when the receiver needs the current complete resource.

Event typeEmitted when
equipment.created.v1Equipment is first committed
equipment.updated.v1Externally visible equipment details change
equipment.status_changed.v1Equipment lifecycle status changes
equipment.deployment_changed.v1Current site or location assignment changes
calibration_requirement.due_status_changed.v1Authoritative status crosses current, due soon, due or overdue
calibration_event.approved.v1A calibration event is approved
calibration_event.rejected.v1A submitted calibration event is rejected
calibration_event.voided.v1A calibration event is formally voided
certificate.available.v1A certificate becomes available
certificate.replaced.v1A retained certificate is superseded
out_of_tolerance.opened.v1An investigation is opened
out_of_tolerance.status_changed.v1Its controlled workflow status changes
out_of_tolerance.closed.v1Quality closure is committed

Example event

{
  "id": "7d2639ae-756f-4e39-991f-87c27ecb77d4",
  "type": "calibration_event.approved.v1",
  "occurred_at": "2026-09-01T12:00:00Z",
  "organisation_id": "bd4c6d3a-5e65-4ee5-8381-6e02ec85270a",
  "resource": {
    "type": "calibration_event",
    "id": "dd82ae65-d861-45bf-ab29-4fe16de4b2e6",
    "version": 4
  },
  "data": {
    "result": "pass",
    "approved_at": "2026-09-01T12:00:00Z"
  }
}

Payloads are minimal snapshots and never include signing secrets, email addresses, storage keys, certificate bytes, full audit documents, raw measurements or unrestricted investigation notes.

Authentication and replay protection

Verify the exact request bytes

Every attempt includes:

Metra-Webhook-Id: <event-id>
Metra-Webhook-Timestamp: <unix-seconds>
Metra-Webhook-Signature: v1=<lowercase-hex-hmac-sha256>
Content-Type: application/json

Metra computes HMAC-SHA-256 over these exact UTF-8 bytes:

<event-id>.<timestamp>.<raw-request-body>

Node.js verification example

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyMetraWebhook(rawBody, headers, secret, now = Date.now()) {
  const id = headers["metra-webhook-id"];
  const timestamp = headers["metra-webhook-timestamp"];
  const supplied = (headers["metra-webhook-signature"] ?? "")
    .split(",").map(value => value.trim().replace(/^v1=/, ""))
    .filter(value => /^[0-9a-f]{64}$/.test(value));
  if (!id || !/^\d{10}$/.test(timestamp ?? "") || supplied.length === 0) return false;
  if (Math.abs(now / 1000 - Number(timestamp)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(id).update(".").update(timestamp).update(".").update(rawBody)
    .digest();
  return supplied.some(value => {
    const received = Buffer.from(value, "hex");
    return received.length === expected.length && timingSafeEqual(received, expected);
  });
}
  • Capture the unmodified raw body before JSON parsing; never re-serialise it for verification.
  • Reject timestamps outside a five-minute tolerance before processing.
  • Decode the signature and use constant-time byte comparison.
  • Deduplicate by Metra-Webhook-Id before applying side effects.
  • During rotation, try each comma-separated v1= signature against each currently trusted secret.

Reliable delivery

Design receivers for at-least-once delivery

Return a 2xx response only after the event has been accepted durably. Metra retries bounded transport failures, timeouts, 408, 409, 425, 429 and 5xx responses with backoff. Most other 4xx responses are terminal.

  • The same public event ID is retained across retries and manual redelivery.
  • Events may arrive more than once and global ordering is not guaranteed.
  • Respond quickly; fetch current resource details asynchronously through the read API.
  • Retry-After is honoured only within safe configured bounds.
  • Repeated failure may pause an endpoint; resume it after correcting the receiver.

Delivery history stores safe status, timing and attempt metadata, not receiver response bodies. Use event and delivery IDs when troubleshooting and never disclose a signing secret or payload in a support request.

Versioning

Compatibility policy

The /v1 path and versioned response schemas form the compatibility boundary. Optional fields may be added within v1, so integrations must ignore unknown response fields. Breaking field, enum or scope changes require a new version or a documented deprecation period.

Read-only means read-only. Version 1 provides no external create, update, delete, transition, approval, import or bulk-export operations.

Read the API key guide Contact support