Skip to main content
These examples use ehr.write, which a new organization does not have until you provision that action and a policy for it. Without them every call here returns deny. On a new account, start with the Developer / API quickstart: it uses the database.execute_sql demo action the console seeds for you.
The canonical AtlaSent surface is two forms of a single execution-boundary contract. Both mint a Permit, verify it end-to-end, and fail closed if anything other than allow happens. The Permit is the artifact you act on; the call site picks the form that fits. Both resolve to the same wire contract (/v1-evaluate + /v1-verify-permit) and produce the same audit-chain entry. You can mix them freely in a codebase.
Do not call /v1-evaluate directly and branch on decision === "allow". That pattern is bypassable, audit-weak, and forces the caller to re-implement verification. Use protect() / with_permit() instead. The raw evaluate() and verify() SDK methods exist only for callers that need to inspect the four-value decision (allow / deny / hold / escalate) or hold a permit token across a boundary.

protect() — acquire a verified Permit

protect() is the primitive. It returns a verified Permit on success and raises on anything else. There is no permitted=False return path. If protect() returns, the action is authorized end-to-end.
The returned Permit is a first-class artifact:
Persist permit_id next to your action record. That is the link that makes the audit chain two-way navigable and that satisfies the electronic-signature record requirement under 21 CFR Part 11 §11.50 (details).

with_permit() / withPermit() — the lexically-scoped form

When the action body is a single scope, bind it to the permit’s lifetime directly. The body runs only on a verified permit; on deny, hold, escalate, verification failure, or transport error it is never invoked.
Same fail-closed contract as protect(). Same audit-chain entry. The difference is purely lexical: the call site reads as one statement that says “execute this body under a permit,” and the verified Permit is visible inside the body if you need it.

What just happened

In a single call, the SDK:
  1. Posted the request to /v1-evaluate. The policy engine returned allow and issued a signed Permit token (pt.v4.…) with a default 1-hour TTL.
  2. Posted the permit to /v1-verify-permit. The signature was checked, the TTL was checked, and the verification was bound into the audit chain.
  3. Returned the verified Permit to you (or, with with_permit() / withPermit(), invoked your body with it).
Both API calls landed in the audit chain. The Permit you hold is Ed25519-signed, single-use, and now inspectable via /v1/permits/{id} or the Console.

Other decisions

protect() and with_permit() raise AtlaSentDeniedError on any non-allow outcome. If you need to inspect the four-value decision (allow / deny / hold / escalate) at the call site — for example, to route hold to an approval queue — drop down to the raw evaluate() primitive:
evaluate() does not verify the permit for you. Callers using the raw primitive must call verify_permit_by_id() / verifyPermitById() before executing — see the next page.
Fail closed. If the API returns 5xx or times out, your agent must treat it as deny. protect() and with_permit() already do this for you (they raise AtlaSentError). If you drop down to raw evaluate(), never fall back to “allow on error” in production — it defeats the entire authorization layer.

Next

protect() already verified the permit for you. The next page covers the explicit verify call — needed only when you hold a permit token that crossed a boundary (e.g. between two services) and the receiving side wants to re-confirm before acting. Verify a permit explicitly →