Skip to content

Any language (HTTP) ​

The FastAPI SDK is a convenience layer over three HTTP endpoints, the SDK API. From Node, Go, Rails, Laravel or anything else, you can do the same job with a few calls.

Authentication ​

Every call sends your API's SDK secret key, from the Connect tab:

http
Authorization: Bearer mas_...

The base URL is https://api.microauth.com. Errors use application/problem+json with a detail you can show in logs. A rotated or wrong secret gets 401, and an API suspended by MicroAuth gets 403.

Keys are matched by hash

Your customers send their key (map_...) to your API. You never forward it to MicroAuth. You send its SHA-256 hex digest instead: 64 lowercase hex characters. The snapshot lists key hashes and the verify endpoint takes a key_hash.

The simple version ​

Two calls cover it. Before your handler runs, look up the key. After the response, report it.

bash
# 1. Check the key a request came with. Send its SHA-256, never the key.
HASH=$(printf %s "$CUSTOMER_KEY" | shasum -a 256 | cut -d' ' -f1)
curl -s "https://api.microauth.com/sdk/v1/keys/verify?key_hash=$HASH" \
  -H "Authorization: Bearer $MICROAUTH_SECRET_KEY"

# 2. Report the request when it's done. Retrying with the same
#    idempotency_key never bills twice.
curl -s -X POST "https://api.microauth.com/sdk/v1/usage" \
  -H "Authorization: Bearer $MICROAUTH_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"items":[{"idempotency_key":"req_8f2c1a9d","api_key_id":"<key_id>",
       "usage_policy_id":"<usage_policy_id>","status_code":200,"count":1,
       "period_start":"2026-10-11T12:00:00Z"}]}'

Cache each lookup for up to 30 seconds and send usage in batches, as in the Node example below. For busy APIs, load the whole snapshot instead, so keys are checked in memory with no call at all.

The pattern at scale ​

text
your API process                        MicroAuth
+------------------------+
| keys and customers     |  every 30s   GET  /sdk/v1/snapshot
| in memory  <-----------+-----------------------------------
|                        |  new key     GET  /sdk/v1/keys/verify
| per request: hash the  +----------------------------------->
| key, look it up, check |
| limits, count it       |  every 5s    POST /sdk/v1/usage
| usage counters --------+----------------------------------->
+------------------------+

A known key never needs a network call. The verify endpoint covers keys created after your latest snapshot, and usage goes out in batches.

GET /sdk/v1/snapshot ​

Everything you need to check keys locally: every customer with their effective limits, and every active key hash.

bash
curl https://api.microauth.com/sdk/v1/snapshot \
  -H "Authorization: Bearer mas_..."
json
{
  "tenant_id": "0199d4f2-6c1e-7a3b-9f10-2b6c8d4e5a71",
  "tenant_name": "Weather API",
  "generated_at": "2026-10-11T12:00:00Z",
  "billable_status_codes": [200, 201, 202, 203, 204, 205, 206],
  "platform_monthly_limit": 1000000,
  "platform_monthly_used": 48210,
  "platform_monthly_remaining": 951790,
  "platform_period_end": "2026-11-01T00:00:00Z",
  "platform_hard_cap": true,
  "customers": [
    {
      "id": "0199d4f2-7a10-7c55-8e2b-41f0d9a3c6e8",
      "status": "active",
      "credit_balance_micro": 12500000,
      "balance_seq": 1842,
      "month_requests": 48210,
      "usage_policy_id": "0199d4f3-0b6e-7f21-a4c9-6d2e8b1f0a37",
      "policy_valid_until": "2026-10-12T12:00:00Z",
      "effective": {
        "rps": 10,
        "price_per_request_micro": 1000,
        "monthly_quota": 100000,
        "source": "plan",
        "billing_model": "subscription"
      }
    }
  ],
  "keys": [
    {
      "id": "0199d4f2-9c3d-7e08-b5a1-27c4e6f8d901",
      "key_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "customer_id": "0199d4f2-7a10-7c55-8e2b-41f0d9a3c6e8"
    }
  ]
}
  • tenant_id and tenant_name are your API's ID and name.
  • status is active, pending (waiting for your approval) or suspended. Only active customers may call your API.
  • effective is the result of limit resolution. monthly_quota is left out when there is no cap, and rps of 0 means no limit.
  • balance_seq versions the balance. It only goes up, so when two values disagree, the higher balance_seq is the newer balance.
  • Keep each customer's usage_policy_id with the cached limits and send it with the usage of requests you admitted under it. Those requests are then charged at the prices that applied when you served them. policy_valid_until is the latest traffic time the policy covers, not a deadline for reporting.
  • platform_monthly_* is your MicroAuth plan's allowance for the calendar month in UTC, not one customer's quota. Refuse requests with 429 once platform_monthly_remaining reaches zero.
  • Refresh about every 30 seconds. The limit is 120 requests a minute per API.
  • Send the last ETag back as If-None-Match. When nothing changed, MicroAuth answers 304 Not Modified with no body and an X-MicroAuth-Generated-At header. Keep your cached data and treat it as fresh as of that time.

GET /sdk/v1/keys/verify ​

Looks up one key hash, for keys that aren't in your snapshot yet because they were created seconds ago.

bash
curl "https://api.microauth.com/sdk/v1/keys/verify?key_hash=e3b0c442..." \
  -H "Authorization: Bearer mas_..."
json
{
  "valid": true,
  "key_id": "0199d4f2-9c3d-7e08-b5a1-27c4e6f8d901",
  "billable_status_codes": [200, 201, 202, 203, 204, 205, 206],
  "customer": {
    "id": "0199d4f2-7a10-7c55-8e2b-41f0d9a3c6e8",
    "status": "active",
    "credit_balance_micro": 12500000,
    "balance_seq": 1842,
    "month_requests": 48210,
    "usage_policy_id": "0199d4f3-0b6e-7f21-a4c9-6d2e8b1f0a37",
    "policy_valid_until": "2026-10-12T12:00:00Z",
    "effective": { "rps": 10, "price_per_request_micro": 1000, "monthly_quota": 100000, "source": "plan", "billing_model": "subscription" }
  }
}

An unknown or revoked key returns {"valid": false} with status 200: the call worked, the key didn't.

  • Only call it when a key isn't in your cache, and let concurrent requests for the same hash share one call.
  • Remember invalid hashes for about 30 seconds, so a flood of made up keys is absorbed by your cache.
  • The limit is 600 requests a minute per API.

POST /sdk/v1/usage ​

Reports served requests in batches. For each item, MicroAuth records the usage, updates the customer's monthly count and charges their balance when the status code is billable.

bash
curl -X POST https://api.microauth.com/sdk/v1/usage \
  -H "Authorization: Bearer mas_..." \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "idempotency_key": "f3cf32d1-e2b8-4d33-a37e-60f5f70b92e8",
        "api_key_id": "0199d4f2-9c3d-7e08-b5a1-27c4e6f8d901",
        "usage_policy_id": "0199d4f3-0b6e-7f21-a4c9-6d2e8b1f0a37",
        "status_code": 200,
        "count": 42,
        "period_start": "2026-10-11T12:00:00Z"
      }
    ]
  }'
json
{
  "accepted": 1,
  "results": [
    {
      "idempotency_key": "f3cf32d1-e2b8-4d33-a37e-60f5f70b92e8",
      "status": "accepted",
      "customer_id": "0199d4f2-7a10-7c55-8e2b-41f0d9a3c6e8",
      "charged_micro": 42000,
      "recorded_at": "2026-10-11T12:34:56.789Z"
    }
  ],
  "customers": [
    { "id": "0199d4f2-7a10-7c55-8e2b-41f0d9a3c6e8", "credit_balance_micro": 12458000, "balance_seq": 1843 }
  ]
}
FieldRules
idempotency_keyRequired. 8 to 128 letters, digits, dots, underscores, colons or dashes. Create it once, before the first send
api_key_idRequired. The key's id from the snapshot or verify
status_codeRequired. The HTTP status your API answered with
countRequired. 1 to 10,000,000 requests
period_startRequired. RFC 3339 with an offset, such as 2026-10-11T12:00:00Z. Bucketed to the hour. At most 45 days old and 5 minutes in the future
usage_policy_idRecommended. The customer's policy from the snapshot. Without it, current prices apply

Each result has a status:

  • accepted means the item was applied now. duplicate means an identical item with that idempotency key was applied earlier. Both are final: remove the item from your queue.
  • rejected comes with http_status and a detail, for example a key from another API. Don't retry it unchanged. Reusing an idempotency key with different content is rejected with 409.
  • retry is temporary. Keep the item and send it again unchanged.

Accepted and duplicate results also carry the receipt MicroAuth keeps for the idempotency key:

FieldMeaning
customer_idThe customer the item was recorded for
charged_microWhat the item charged, in micro-USD. 0 when the status isn't billable. A duplicate returns the original charge
recorded_atWhen MicroAuth first recorded the item. A duplicate returns the original time

Keep receipts if you reconcile your own records with MicroAuth's ledger. An item with a billable status is charged its count times the price of the policy it was sent with, so you can check every receipt against what you expected to charge.

customers holds the balance of every customer in the batch after it was charged. When its balance_seq is higher than the one you hold, replace your cached balance and drop the local holds for the items just acknowledged.

Report every authenticated response, billable or not, with its real status. Quotas and your plan's allowance count all of them, and only billable statuses cost money. A batch holds 1 to 1000 items, and the limit is 600 requests a minute per API. If a call fails or times out, keep the items and retry with backoff: the idempotency key makes the retry safe.

Checking a request ​

For each incoming request:

  1. Read the key from your header, X-API-Key by default. No key means 401.
  2. Hash it and look it up in your snapshot. If it's missing, call verify. Still unknown means 401.
  3. A customer whose status isn't active gets 403.
  4. If billing_model isn't none and the price is above zero, a balance below the price gets 402. Subtract what you counted since the snapshot.
  5. If there's a monthly_quota and month_requests plus your local count reaches it, answer 429.
  6. If platform_monthly_remaining is used up, answer 429.
  7. Apply effective.rps per customer. Over the limit means 429 with Retry-After. With more than one process, keep the counter in Redis.
  8. Serve the request, then count it by key, policy, status code and hour.

Node example ​

An Express middleware that checks keys with a 30 second cache, enforces suspension, balance and quota, and reports usage every 5 seconds. It keeps its state in memory, so it suits one process. For several processes, or strict per second limits, use the snapshot and Redis as described above.

js
import { createHash, randomUUID } from "node:crypto";

const BASE = "https://api.microauth.com/sdk/v1";
const AUTH = { Authorization: `Bearer ${process.env.MICROAUTH_SECRET_KEY}` };
const cache = new Map();
const pending = [];

async function lookup(hash) {
  const hit = cache.get(hash);
  if (hit && Date.now() - hit.at < 30_000) return hit.result;
  const res = await fetch(`${BASE}/keys/verify?key_hash=${hash}`, { headers: AUTH });
  if (!res.ok) throw new Error(`MicroAuth answered ${res.status}`);
  const result = await res.json();
  cache.set(hash, { at: Date.now(), result });
  return result;
}

export function microauth(header = "x-api-key") {
  return async (req, res, next) => {
    const key = req.get(header);
    if (!key) return res.status(401).json({ error: "Missing API key" });
    const hash = createHash("sha256").update(key).digest("hex");
    let v;
    try {
      v = await lookup(hash);
    } catch {
      return res.status(503).json({ error: "Try again shortly" });
    }
    if (!v.valid) return res.status(401).json({ error: "Invalid API key" });
    const c = v.customer;
    const e = c.effective;
    if (c.status !== "active") return res.status(403).json({ error: "This account is suspended" });
    const price = e.billing_model === "none" ? 0 : e.price_per_request_micro;
    if (price > 0 && c.credit_balance_micro < price) return res.status(402).json({ error: "Add credit to continue" });
    if (e.monthly_quota && c.month_requests >= e.monthly_quota) return res.status(429).json({ error: "Monthly quota reached" });

    req.customer = c;
    res.on("finish", () => {
      pending.push({
        idempotency_key: randomUUID(),
        api_key_id: v.key_id,
        usage_policy_id: c.usage_policy_id,
        status_code: res.statusCode,
        count: 1,
        period_start: new Date().toISOString(),
      });
    });
    next();
  };
}

setInterval(async () => {
  const items = pending.splice(0, 1000);
  if (!items.length) return;
  try {
    const res = await fetch(`${BASE}/usage`, {
      method: "POST",
      headers: { ...AUTH, "Content-Type": "application/json" },
      body: JSON.stringify({ items }),
    });
    const body = res.ok ? await res.json() : { results: [] };
    const done = new Set(body.results.filter((r) => r.status !== "retry").map((r) => r.idempotency_key));
    pending.unshift(...items.filter((i) => !done.has(i.idempotency_key)));
  } catch {
    pending.unshift(...items);
  }
}, 5_000).unref();
js
import express from "express";
import { microauth } from "./microauth.js";

const app = express();
app.get("/forecast", microauth(), (req, res) => res.json({ customer: req.customer.id }));
app.listen(8000);

Before you rely on it in production, bound the pending queue, keep it on disk so a restart doesn't lose usage, and drain it on shutdown.

See also ​

  • The API reference has every field of these endpoints.
  • Troubleshooting helps when the dashboard never shows your API as connected.
  • The platform API manages customers, credit and plans from your own systems.

MicroAuth is a product of Zyref, LLC.