v4.2.0

Reference

API reference

Twelve endpoints, one of which is the reason the other eleven exist. Everything is idempotent except withdrawal, which is idempotent by nature.

base
https://api.iamsec.si/v4
Authorization: Bearer <session-token>
Idempotency-Key: <uuid>   # required on all writes except /revocations

The Idempotency-Key requirement exists because a half-finished negotiation retried twice is a policy nobody signed. Revocations are exempt from needing one: an already-revoked grant returning 204 is not a failure state, and the endpoint will never ask you to prove you meant it.

Endpoints

MethodPathDoesLatency p99
POST/policiesDraft a proposal. Grants nothing38ms
POST/policies/{id}/narrowCounterparty removes scope; cannot add31ms
POST/policies/{id}/affirmBind a factor to a policy version44ms
GET/policies/{id}Fetch the document and its signature chain12ms
POST/sessionsMint a window from a signed policy52ms
GET/sessions?state=activeEnumerate live windows9ms
POST/evaluateThe hot path. Every call re-resolves1.8ms
POST/checkinsLiveness. A gap past the ceiling revokes6ms
POST/limitsDeclare a hard or soft limit0.9ms
POST/revocationsEverything, now. No reason field0.6ms
GET/auditYour events, mutually redacted71ms
POST/aftercare/ackDischarge an aftercare obligation23ms

POST /v4/evaluate

Called on every protected operation, not once per session. It returns a decision, the statement that produced it, and the age of the affirmation behind it.

request
{
  "session": "sess_01jd9",
  "actor": "top/a-2210",
  "subject": "bottom/b-4417",
  "action": "contact.restraint",
  "context": {
    "monitor_present": false,
    "sober_attested_at": 1763061120,
    "elapsed_in_window_ms": 412000
  }
}
response — 200, decision: deny
{
  "decision": "deny",
  "matched_statement": "negotiated/step-up",
  "precedence": "capacity_gate",
  "failed_gate": "monitor_present",
  "overridable": false,
  "appeal": null,
  "affirmation_age_ms": 1820000,
  "scope_max_age_ms": 5400000,
  "next": "a monitor must hold before this action evaluates true. nothing else changes"
}

POST /v4/revocations

The endpoint the whole product is arranged around. Its request body is nearly empty by intent, and the schema will not accept a reason if you try to send one.

the entire request body
{ "scope": "all" }
204 No Content
HTTP/1.1 204 No Content
X-Iamsec-Revoked: 7
X-Iamsec-Propagation-Ms: 0.6
X-Iamsec-Aftercare: opened, 6h, obligatory
PropertyValue
Reason fieldDoes not exist. Unknown fields are rejected, including reason, why and justification
AuthenticationRequired — and only the actor's own. No counterparty signature, no admin co-sign
Rate limitsExempt, permanently, on every plan. See below
Idempotency keyNot required. Second call returns 204 as well
BlockingSynchronous. The response lands after invalidation is committed, not queued
UndoNone. There is no DELETE /revocations. Resumption is a new policy

Errors

Error semantics matter more than endpoint lists here, because most of these states are things a client should not retry.

CodeSlugMeansRetry?
400malformed_policyThe document is not representable. Usually a wildcardno
401affirmation_expiredYour factor aged out. Re-affirm, do not re-askafter affirming
403hard_limit_immutablePlatform deny. Not a bug, not a plan limitation, not appealablenever
409window_supersededA newer signed version exists; this one is frozenno
410consent_withdrawnThe grant is gone and will not come back as this objectnever
422scope_ceiling_exceededYou asked for a longer max age than the tier allowsafter narrowing
428affirmation_requiredStep-up gate not met for a high or critical scopeafter affirming
429rate_limitedOrdinary backpressure. Never returned by /revocationswith jitter
451negotiated_in_bad_faithReserved, and permanently so. See the note belownever

Rate limits

Limits are per subject and generous, with one absolute exemption.

Endpoint classLimitExceeding it
/evaluate10,000/min/subject429 with jitter hint
/policies*, /sessions*600/min/subject429
/audit60/min/subject429, then a conversation
/revocationsunlimited, foreverNot possible. A limiter here is a denial of service against consent itself

Webhooks

payload — consent.withdrawn
{
  "id": "evt_9f2c",
  "type": "consent.withdrawn",
  "occurred_at": "2026-11-13T21:41:07.412Z",
  "data": {
    "actor": "bottom/b-4417",
    "breadth": "all",
    "sessions_revoked": 7,
    "propagation_ms": 0.6,
    "aftercare_deadline": "2026-11-14T03:41:07Z"
  }
}
EventFires when
policy.signedBoth parties have bound a current factor to the same version
session.openedA window is minted and its token is live
checkin.missedA heartbeat gap passed the scope ceiling — revocation has already happened
consent.withdrawnAny party revoked. Delivery is best-effort; the revocation is not
hard_limit.attemptedSomeone tried to grant or override a reserved resource
aftercare.sla_breachedAn obligation went undischarged past its deadline. This one pages

SDKs

RuntimePackageNotes
Node 20+@iamsec/sdkTyped, edge-safe, no ambient credentials
Python 3.11+iamsecSync and async clients share one policy object
Go 1.23+github.com/iamsec/iamsec-goThe reference implementation of the evaluator
Rustiamsec-coreWASM build for on-device check-ins with no network path