Usage & diagnostics

Usage and diagnostics

What Havnly actually received from you. When an integration misbehaves you can see what your code believes it sent; this is the other half.

GET /usage
{
  "data": {
    "window": { "since": "2026-09-21T19:15:13Z", "until": "2026-09-22T19:15:14Z" },
    "requests": {
      "total": 28, "ok": 22,
      "client_errors": 6, "server_errors": 0, "rate_limited": 0,
      "error_rate_pct": 21.4
    },
    "latency_ms": { "p50": 670, "p95": 2491, "slowest": 2748 },
    "by_path": [
      { "path": "/claims", "method": "POST", "calls": 8, "errors": 2, "p95_ms": 1679 },
      { "path": "/claims/{ref}/dates", "method": "POST", "calls": 5, "errors": 3, "p95_ms": 2748 }
    ],
    "rate_limit": { "per_minute": 600, "remaining": 573, "resets_at": "2026-09-22T19:16:00Z" }
  }
}

The default window is the last 24 hours — enough to cover an overnight batch. Pass ?since= for anything else.

by_path collapses identifiers, so a thousand calls against a thousand requests read as one line rather than a thousand. It is the fastest way to see that your errors are concentrated in one call rather than scattered.

rate_limited is counted separately from other 4xx. Being throttled is a different problem from sending bad requests, and adding them together sends people looking in the wrong place.

The calls themselves

GET /usage/requests?errors=true
{
  "data": [
    {
      "key": "hvn_sk_e5e91f221",
      "method": "POST", "path": "/claims/REQ-95A4A7/dates",
      "status": 409, "duration_ms": 612,
      "error": null, "at": "2026-09-22T17:33:29Z"
    }
  ]
}

This is the one that ends an argument about whether a request arrived. Each row names the key prefix that made it, which is enough to tell a nightly batch from a live system without exposing anything secret.

ParameterMeaning
errors=trueOnly 4xx and 5xx
statusComma-separated codes, e.g. 409,422
methodGET, POST, PATCH, DELETE
sinceISO timestamp. Default 24 hours ago
limit, cursorUp to 250 a page

Request logs are kept for 30 days.

Your keys

GET /usage/keys
{
  "data": [
    {
      "prefix": "hvn_pk_00a8f5040",
      "environment": "live",
      "scopes": ["claims:read", "claims:write"],
      "is_active": true,
      "rate_limit_per_minute": 600,
      "last_used_at": "2026-09-21T21:41:38Z",
      "in_use_now": false
    }
  ]
}

Never a secret, only a prefix. last_used_at is the column worth watching: a key nobody has used in months is a key that should not still work.

Turning off a key you have lost

POST /usage/keys/self/revoke

The key making the call stops working immediately. Use it when you believe a key has leaked and you want it dead before you can reach a browser.

A key can only revoke itself. If a leaked key could disable its siblings, the leak would become a way to take your whole integration down — revoking the key in your own hand is the one action that cannot be turned against you.

To issue a replacement, or to disable a different key, use Havnly → API Console. That deliberately needs a person.