Developer Guide

Connect your retail or brand site to Axiom and pull your own orders, sample status, and certificates of analysis straight into your systems, then show published certificates on your product pages. This guide covers getting your keys, connecting securely, the full order-to-COA lifecycle, and how batches map to certificates. The goal is less back-and-forth: your site can reflect testing status in real time and display each certificate the moment we publish it.

1. Your two keys

Axiom gives every account two distinct keys with very different security profiles. Use the right one for the job.

Public embed key (axk_live_pub_…)

Non-secret. Safe to paste into your website HTML or front-end code. It can only reach certificates you have already published (released and public), which are world-readable anyway. It powers the embed widgets and the public read API. It cannot read orders, submit samples, or see anything unpublished.

Secret API key (ax_live_…)

Secret. Treat it like a password: keep it on your backend only, never in browser code, a mobile app, or a public repository. It authenticates server-to-server calls to pull your orders and sample status, and (if granted) submit samples. Keys are scoped, hashed at rest, and rate-limited. We show the secret once at creation and store only a hash, so if you lose it you create a new one.

2. Get your keys

Sign in to your Axiom portal and open Embed & Integrate. Your public embed key is shown at the top. To create a secret API key, give it a label (for example "Production site"), choose its scopes, and click Create. Copy the secret immediately; it will not be shown again. You can revoke a key at any time, which takes effect instantly.

Scopes

Grant the minimum a key needs:

  • coas:read – list and fetch your published certificates.
  • orders:read – track orders and sample status across the lifecycle.
  • samples:submit – create orders and submit samples programmatically.

3. Connect securely

A few rules keep both sides safe:

  • Send your secret key in the Authorization: Bearer header over HTTPS only. Never put it in a URL, query string, or client-side code.
  • Store the key in an environment variable or a secrets manager on your server. Do not commit it.
  • Use one key per integration and scope it minimally. Revoke and rotate by creating a new key, switching to it, then revoking the old one.
  • If a key is ever exposed, revoke it immediately in the portal and create a replacement.
  • Every call is tenant-scoped: a key only ever returns data for the account that owns it. There is no way to read another company's orders or unpublished certificates.
  • For anything that runs in a browser, use the public embed key, never the secret key.

A first call to confirm your key works:

bash
curl https://axiomanalyticslab.com/api/v1/me \
  -H "Authorization: Bearer ax_live_xxxxxxxx_..."

4. The order to COA lifecycle

Every sample moves through an ordered set of stages. The lifecycle field on a sample tells you exactly where it is, so your site can render a live progress tracker from submit to published without emailing us.

lifecycleWhat it means
submittedOrder placed; we are awaiting your vial.
receivedYour vial has arrived at the lab.
validatedStaff have validated the vial identity and condition.
in_analysisUndergoing the 3X or 7X analysis at our partner lab.
data_reviewResults are in; the certificate is being prepared and signed.
publishedThe certificate is released, verifiable, and embeddable.

5. Orders, batches and certificates

How the pieces relate, so you can map a certificate back to one of your products:

model
Order (ORD-2026-00042)
  └─ Sample  (your product + lot number)   ── lifecycle ──▶ published
       └─ Certificate (AX-2026-00031)       ◀── one COA per sample when published

Your lotNumber is the batch handle that ties everything together: you set it at submission, it stays on the sample, and it appears on the published certificate. To show the right certificate on a product page, match by that lot number (see the per-product embed and the by-lot endpoint below). One order can carry many samples; each sample yields one certificate when its analysis is published.

6. API reference

Base URL https://axiomanalyticslab.com. All /api/v1 endpoints require a secret key in the Authorization header and return JSON. Responses are tenant-scoped to your account.

GET/api/v1/me
Confirms a key works; returns your account and the key's scopes.
GET/api/v1/ordersscope: orders:read
Lists your orders, most recent first. Paginate with ?limit and ?cursor.
GET/api/v1/orders/{orderNumber}scope: orders:read
Full order detail: every sample with its lifecycle stage and the linked certificate once published. Poll this to drive a progress tracker.
GET/api/v1/samples/{id}scope: orders:read
Single sample status, lifecycle stage, and certificate when published.
GET/api/v1/coasscope: coas:read
Lists your published certificates (released and public only). Paginate with ?limit and ?cursor.
GET/api/v1/coas/{id}scope: coas:read
One certificate descriptor plus the live cryptographic verdict.
POST/api/v1/samplesscope: samples:submit
Create an order and submit samples. Returns the ship-to address and a blind shipment id per vial. Orders land unpaid; payment is settled separately before analysis begins.

Examples

bash
# Track an order's samples and certificates
curl https://axiomanalyticslab.com/api/v1/orders/ORD-2026-00042 \
  -H "Authorization: Bearer $AXIOM_KEY"
bash
# Submit samples programmatically
curl -X POST https://axiomanalyticslab.com/api/v1/samples \
  -H "Authorization: Bearer $AXIOM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "testBundle": "7x",
    "samples": [
      { "productName": "Retatrutide 10mg", "lotNumber": "RT-10MG-0042",
        "clientLabelClaim": "10 mg" }
    ]
  }'
json
// GET /api/v1/orders/ORD-2026-00042 (shape)
{
  "orderNumber": "ORD-2026-00042",
  "status": "in_processing",
  "samples": [{
    "productName": "Retatrutide 10mg",
    "lotNumber": "RT-10MG-0042",
    "lifecycle": "in_analysis",
    "coa": null
  }]
}

7. Embed certificates

For anything client-side, use your public embed key and one script tag. No secret, no build step. Full snippets (single certificate, full library, per-product) are on your Embed & Integrate page.

html
<!-- A searchable library of all your published certificates -->
<div data-axiom-library="axk_live_pub_xxxxxxxxxxxxxxxxxxxxxxxx"></div>

<!-- The certificate for one product lot, on a product page -->
<div data-axiom-product="RT-10MG-0042"
     data-axiom-client="axk_live_pub_xxxxxxxxxxxxxxxxxxxxxxxx"
     data-match="lot"></div>

<script async src="https://axiomanalyticslab.com/embed.js"></script>

The same data is available as JSON, with no key required, for your published certificates:

bash
curl "https://axiomanalyticslab.com/api/public/clients/axk_live_pub_xxxx/coas?q=retatrutide"
curl "https://axiomanalyticslab.com/api/public/coas/by-lot?client=axk_live_pub_xxxx&lot=RT-10MG-0042"

8. Webhooks

Instead of polling, register a webhook on your Embed & Integrate page and we POST a coa.released event to your endpoint the moment a certificate publishes. Your endpoint must be a public https URL. On creation we show a signing secret once; use it to verify each request.

Each delivery carries these headers and a JSON body:

http
POST /your-endpoint
Content-Type: application/json
Axiom-Event: coa.released
Axiom-Webhook-Id: <unique delivery id, dedupe on this>
Axiom-Signature: t=<unix>,v1=<hex hmac-sha256>

{
  "type": "coa.released",
  "data": {
    "coaId": "AX-2026-00031", "version": 1, "lotNumber": "RT-30MG-...",
    "productName": "Retatrutide", "sha384": "...",
    "verifyUrl": "...", "embedUrl": "...", "apiUrl": "..."
  }
}

Verify the signature (the body is HMAC-SHA256 of `t`.`rawBody`) and reject stale timestamps:

javascript
import crypto from "node:crypto";

export function verifyAxiom(rawBody, header, secret) {
  const [tPart, vPart] = header.split(",");        // "t=...", "v1=..."
  const t = Number(tPart.slice(2));
  const sig = vPart.slice(3);
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // 5 min skew
  const expected = crypto.createHmac("sha256", secret)
    .update(\`\${t}.\${rawBody}\`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Deliveries are at-least-once with retry/backoff, so dedupe on Axiom-Webhook-Id. Treat the webhook as a notification plus a sha384 anchor; fetch the apiUrl for the verified certificate body.

Event types — subscribe to the full lifecycle

New endpoints subscribe to all four events by default. coa.revoked, coa.amended, and coa.superseded are safety events: when you receive one, downgrade or re-fetch the affected certificate immediately so you never keep showing a pulled cert as "verified." Each payload carries a normalized data.state plus revokedAt / revocationReason / supersededBy.

json
// coa.revoked
{
  "type": "coa.revoked",
  "data": {
    "coaId": "AX-2026-00031", "state": "revoked",
    "revokedAt": "2026-06-27T18:00:00.000Z",
    "revocationReason": "Lot recalled by manufacturer",
    "verifyUrl": "...", "apiUrl": "..."
  }
}

Test and replay from your Embed & Integrate page: send a signed coa.test event, view the delivery log, and replay any delivery.

9. Verifying a certificate (offline)

The live verdict endpoint is the easy path — no key required:

bash
curl https://axiomanalyticslab.com/api/coa/AX-2026-00010

But you do not have to trust our server. Every certificate is sealed with Ed25519 over a SHA-384 digestof a canonical payload, and we publish everything needed to verify it fully offline: the public keys (JWKS), the exact recipe, and a self-contained test vector.

bash
curl https://axiomanalyticslab.com/.well-known/jwks.json                  # org seal + 3 staff public keys
curl https://axiomanalyticslab.com/.well-known/axiom-coa-signing.json     # the exact signing recipe (axiom-coa-sig-v1)
curl https://axiomanalyticslab.com/.well-known/axiom-coa-testvector.json  # a real cert that verifies, for your test suite

The verdict response includes a signing block with the exact bytes that were signed. Hash that string with SHA-384 to reproduce sha384, then verify each signature against the JWKS key with the matching kid. Ten lines, any language:

javascript
import { createHash } from "node:crypto";
import { ed25519 } from "@noble/curves/ed25519.js";

const r = await (await fetch("https://axiomanalyticslab.com/api/coa/AX-2026-00010")).json();
const jwks = await (await fetch("https://axiomanalyticslab.com/.well-known/jwks.json")).json();
const s = r.signing;

// 1. reproduce the digest from the exact published bytes
const sha = createHash("sha384").update(Buffer.from(s.canonicalPayload, "utf8")).digest("hex");
if (sha !== s.sha384) throw new Error("digest mismatch - altered");

// 2. verify each signature (org seal + each staff role) against its JWKS key by kid
for (const sig of s.signatures) {
  const jwk = jwks.keys.find((k) => k.kid === sig.kid);
  const pub = Buffer.from(jwk.x, "base64url");
  const ok = ed25519.verify(Buffer.from(sig.signature.slice(8), "hex"), Buffer.from(s.sha384, "hex"), pub);
  if (!ok) throw new Error("bad signature: " + sig.role);
}
console.log("authentic:", true, "| standing:", r.state); // standing is a separate, live check

Authenticity vs. standing. The signature proves the certificate is genuine and unaltered — this is true forever, even after revocation (status is not part of the signed bytes, exactly like X.509/OCSP). Whether a cert is still in force is a separate, live property: read state (or subscribe to coa.revoked). verified === signature.authentic && state === "valid".

10. Revocation & standing

Certificates can be revoked or superseded. The verdict always carries a normalized state: valid, revoked, superseded, amended, plus the transition fields. A revoked certificate flips verified to false and is served no-store so it can never be cached as valid.

json
// GET /api/coa/<id> for a revoked certificate
{
  "verified": false,
  "signature": { "authentic": true, "algorithm": "Ed25519", "digest": "SHA-384" },
  "state": "revoked",
  "revokedAt": "2026-06-27T18:00:00.000Z",
  "revocationReason": "Lot recalled by manufacturer",
  "supersededBy": null
}

Build for this: drive your badge off verified (not just the signature), honor the coa.revoked webhook, and re-fetch (or subscribe) rather than cache a verdict indefinitely.

11. Rate limits, status & versioning

Public reads are rate limited per IP. Every response carries RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset; a 429 adds Retry-After. Back off on those headers.

Stable endpoints live under /api/v1 (the unversioned public paths remain as permanent aliases). The machine-readable contract is the OpenAPI spec; service health is a single poll:

bash
curl https://axiomanalyticslab.com/api/v1/openapi.json   # OpenAPI 3.1 contract
curl https://axiomanalyticslab.com/api/status            # health + webhook backlog + version
curl https://axiomanalyticslab.com/api/v1/export -H "Authorization: Bearer $AXIOM_KEY"  # export all your data (offboarding)

Compliance docs: DPA, SLA, Security & breach notification, Subprocessors.

Questions about integrating? Your Embed & Integrate page has your live keys and copy-paste snippets.