Skip to main content

Overview

Organization governance is four independent mechanisms:
Policies bind Employees, not Masters. A Master API key never sees a business-hours or spending-limit rejection. The one exception is the address-book whitelist, described below.

Reading and updating the policy

Reading the policy is open to any org member with a PolicyManagement key. Writing it is Master-only.
Server-side validation on PATCH:
  • Turning on businessHoursEnabled requires businessHoursStart, businessHoursEnd and businessHoursTimezone in the same request. Missing any of them is a 400.
  • dailySpendingLimitUsd must be a positive numeric string. Pass explicit null to clear the limit.
  • Every change writes an Org Policy Updated audit row.
No passkey or 2FA step-up material is needed from the SDK for this call.

What each policy does

forceEmployeeKyc

When the policy requires KYC and the caller is not verified, every spend-capable request is rejected with 403. KYC applies to the user, and is never bypassed because the transport is an API key.

businessHoursEnabled

The current time is evaluated in businessHoursTimezone, for Employees only. A request outside the businessHoursStart–businessHoursEnd window, or on a weekday not listed in businessHoursDays, is rejected with 403. Pure key-management steps are exempt, so an employee can still accept a wallet handover or rotate their keys after hours. Anything that signs, exports or spends never qualifies for that exemption.

forceAddressBook

Every destination is checked against the Corporate Address Book (see Organization Wallets). Only verified entries count. An unlisted destination is rejected with 400 "ADDRESS_NOT_WHITELISTED: <address>".
Through an API key a Master is subject to the whitelist exactly like an Employee. This is the one policy that binds Masters.

dailySpendingLimitUsd

See the next section.

Daily spending limits

dailyLimitUsd and remainingUsd are null together when no limit is set, and the server also returns null when the stored value is non-numeric or <= 0. Render an “unlimited” state; treating null as 0 blocks every spend in the UI.
The counter runs on UTC days, so resetAt is always the next UTC midnight regardless of the org’s business-hours timezone. Available to Master and Employee; requires ViewBalances.

How a spend is charged

The limit is evaluated in USD, per organization, and it is charged when the spend is created. Over the limit, the request is refused with 403 "Action blocked: Daily spending limit exceeded." If a token’s price cannot be resolved while a limit is active, the spend is blocked rather than let through. The amount charged is the one the server reads out of the transaction itself, not one the client reports, so an employee cannot understate a spend to stay under the limit. The SDK handles that exchange inside sendTransaction; there is nothing for you to call.

Limit groups

A limit group caps how many wallets and vaults the organization may hold per chain. It is provisioned platform-side; the SDK only reads it.
Only chains the org has a quota for appear in limits. Master or Employee; requires ViewBalances. A 404 here means something is wrong. Treat it as a hard failure, not as “no group”.

Policy rejections in the SDK

OrgPolicyRejectedError is the intended typed surface for these, carrying platformCode: OrgPolicyCode.
OrgPolicyRejectedError is not thrown by the current release, so an instanceof branch on it never fires. Until it is, policy rejections arrive as ordinary HTTP failures:Detect policy rejections from the status code and the message today, as below. The switch to the typed error will be additive, so a catch written that way keeps working.
Detecting a policy rejection as things stand:
The HTTP status lives in error.statusCode on ApiError but in error.details.statusCode on PermissionDeniedError, which is why the helper reads it from two places. Both carry the server’s message on error.message. See Errors.
Once the typed mapping is switched on, this helper can be replaced with an instanceof OrgPolicyRejectedError check keyed on error.platformCode, touching only your catch block.

Audit logs

Master-only, and the key must carry AuditLogAccess. An unrecognised value in eventTypes is a 400.
The declared OrgAuditLogItem type does not match what the server returns. The response is actually:
Read the extra fields through a cast until the type is corrected.

Event types

OrgAuditEventType enumerates the labels the SDK types. Each is triggered by:
More labels appear in the response than the SDK’s union covers. Treat item.event as a string when matching, and do not assume the union is exhaustive.

CSV export

Master-only; the key must carry AuditLogAccess. The method resolves with the raw fetch Response so you can stream it.
Same filters as getOrgAuditLogs, minus pagination. The export streams the entire filtered result set. The response is Content-Type: text/csv with Content-Disposition: attachment; filename="org-audit-logs-YYYY-MM-DD.csv". The header row is fixed:
Date is the ISO 8601 timestamp, Metadata is the JSON blob serialized and CSV-escaped (embedded quotes doubled). Fields containing a comma, quote, or newline are quoted.

Method reference

Last modified on October 22, 2026