Policies
Fystack policies are programmable guardrails for sensitive wallet actions. A policy targets a workspace, optionally narrows to specific wallets, and contains rules that evaluate an expression against a structured action payload.
Rules run for four actions:
- Name
- withdrawal.create
- Type
- trigger_action
- Description
Evaluated before a withdrawal is created. A
DENYblocks the request. A matchingALLOWbypasses transaction approval and lets the withdrawal move to signing.
- Name
- web3.transfer
- Type
- trigger_action
- Description
Evaluated when a native-coin or ERC-20 transfer is sent through the Web3 raw-transaction API. Uses the same
withdrawal.*fields aswithdrawal.create, so one condition reads the same on both triggers. Velocity fields are not available, because these transfers are not withdrawals. ERC-20 transfers are also evaluated asweb3.contract_call.
- Name
- web3.contract_call
- Type
- trigger_action
- Description
Evaluated before EVM contract calls and ERC-20 token transfers are sent to signing. A
DENYrecords the activity asdenied_by_policy. A matchingALLOWskips transaction approval and proceeds to signing.
- Name
- web3.sign_message
- Type
- trigger_action
- Description
Evaluated when a wallet is asked to sign a message with
personal_sign,eth_sign, oreth_signTypedData_v4. Deny only: a signed message always goes through approval.
The policy schema is generated from the backend action schema in Apex. Use Get the policy schema when building UI builders or validating field availability dynamically. For ready-made rules, see Policy Templates.
Policy structure
A policy is the container. A rule is the executable unit.
{
"name": "Large withdrawal guard",
"approval_group_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"wallet_ids": [],
"rules": [
{
"trigger_action": "withdrawal.create",
"condition": "withdrawal.value_usd > 10000 && !withdrawal.is_whitelisted",
"effect": "DENY"
}
]
}
- Name
- approval_group_id
- Type
- string
- Description
The approval group that reviews policy creation, policy edits, policy deletion, and rule changes.
- Name
- wallet_ids
- Type
- string[]
- Description
Wallets this policy applies to. An empty array means every wallet in the workspace.
- Name
- rules
- Type
- array
- Description
Up to 50 rules. Each rule has a
trigger_action,condition,effect, optionaldescription, and optionalis_enabled.
Policy statuses
- Name
- pending
- Type
- status
- Description
Created but not approved yet. Pending policies are not evaluated.
- Name
- active
- Type
- status
- Description
Approved and live. Only active policies are loaded by the evaluator.
- Name
- inactive
- Type
- status
- Description
Disabled and not evaluated.
- Name
- rejected
- Type
- status
- Description
Rejected during approval and not evaluated.
Policy evaluation
For each guarded request, Fystack loads active policies for the workspace, compiles enabled rules, filters matches by action and wallet targeting, then resolves the result:
- If any matching rule has
effect: "DENY", the action is blocked. - Otherwise, if any matching rule has
effect: "ALLOW", the action proceeds and transaction approval is bypassed. - Otherwise, no policy rule matched. The action continues through the normal approval flow.
DENY always wins over ALLOW, so rule order never changes the outcome. Within the winning effect, the first match in declaration order is used for attribution, so the API can surface a stable rule_id and policy reason.
There is no separate "require approval" effect. Approval is the default for any action that no ALLOW rule matches.
Evaluation is fail-closed. Cache, database, compile, or runtime evaluation failures deny the action. Each evaluation has a 100 ms timeout.
Scope and caching
Policies are scoped by workspace. Apex caches a compiled policy bundle per workspace and invalidates it after policy approvals, rejections, and wallet targeting changes. Wallet filtering happens during evaluation, so one workspace bundle can serve all wallets in that workspace.
Conditions
condition is an expression that must return a boolean. Reference fields by section and field name, for example principal.role, withdrawal.value_usd, wallet.purpose, or resource.method_selector.
Use normal boolean, comparison, and membership operators:
&& || ! == != > >= < <= in
in checks membership in a list literal or a list field, for example principal.api_key_id in ["<api_key_id>"] or "0x…" in resource.permit_tokens.
Examples:
- Name
- Deny large unwhitelisted withdrawals
- Type
- DENY
- Description
withdrawal.value_usd > 10000 && !withdrawal.is_whitelisted
- Name
- Cap each wallet at $100,000 per day
- Type
- DENY
- Description
wallet.projected_outflow_24h > 100000
- Name
- Keep custody funds inside the workspace
- Type
- DENY
- Description
wallet.purpose == "custody" && !withdrawal.destination_is_internal
- Name
- Bypass approval for a payout API key
- Type
- ALLOW
- Description
principal.initiator_type == "api_key" && principal.api_key_id in ["<api_key_id>"] && withdrawal.value_usd < 1000
- Name
- Bypass approval for aged address book entries
- Type
- ALLOW
- Description
withdrawal.is_whitelisted && withdrawal.address_age_sec >= 86400 && withdrawal.value_usd <= 1000
- Name
- Deny weekend high-value withdrawals
- Type
- DENY
- Description
(context.day_of_week == "Saturday" || context.day_of_week == "Sunday") && withdrawal.value_usd > 25000
- Name
- Deny high-gas ERC-20 transfers
- Type
- DENY
- Description
resource.method_selector == "0xa9059cbb" && resource.gas_limit > 100000
- Name
- Deny unlimited token permits
- Type
- DENY
- Description
resource.is_permit && resource.permit_amount_is_unlimited
resource.decoded_args is dynamic. Guard dynamic fields with has(...)
before reading them, for example has(resource.decoded_args.to) && resource.decoded_args.to == "0x000000000000000000000000000000000000dead".
Reference fields
Fields marked with a ref in the schema hold the ID or code of another workspace entity: workspace_user, api_key, wallet, address_book, network, or asset_symbol. Condition builders should render a picker for these fields.
When a rule is saved, Fystack checks that each referenced ID belongs to your workspace. If an entity is later deleted or revoked, the rule keeps the ID and can only match less, never more.
Action schema
The action schema defines the fields available inside a condition. Fields marked deprecated are still evaluated for existing rules but should not be used in new ones.
Common fields
Every trigger action exposes the following fields.
- Name
- principal.user_id
- Type
- string · ref workspace_user
- Description
ID of the workspace member who initiated the action. Empty when the initiator is an API key.
- Name
- principal.role
- Type
- string
- Description
Workspace role. One of
owner,admin,signer,proposer, orviewer.
- Name
- principal.initiator_type
- Type
- string
- Description
userfor a web session, orapi_keyfor an API key.
- Name
- principal.api_key_id
- Type
- string · ref api_key
- Description
ID of the API key that initiated the action. Empty for web sessions, so combine it with
principal.initiator_type.
- Name
- principal.user_email
- Type
- string · deprecated
- Description
Email of the initiating user. Use
principal.user_idinstead, because an email can change.
- Name
- wallet.id
- Type
- string · ref wallet
- Description
Source wallet ID. Use it for rules that target specific wallets inside one policy.
- Name
- wallet.purpose
- Type
- string
- Description
Source wallet purpose. One of
general,custody,payment,user,one_time_use,gas_tank, ordeployment.
- Name
- wallet.type
- Type
- string
- Description
Source wallet type.
standardormpc.
- Name
- context.hour
- Type
- integer
- Description
UTC hour of evaluation,
0through23.
- Name
- context.day_of_week
- Type
- string
- Description
UTC weekday name, for example
MondayorSaturday.
Withdrawal fields
Available on withdrawal.create and web3.transfer.
- Name
- withdrawal.amount
- Type
- number
- Description
Requested asset amount.
- Name
- withdrawal.value_usd
- Type
- number
- Description
Requested value in USD.
- Name
- withdrawal.asset_symbol
- Type
- string · ref asset_symbol
- Description
Asset symbol, such as
USDT,ETH, orBTC.
- Name
- withdrawal.asset_is_native
- Type
- boolean
- Description
Whether the asset is the network native asset.
- Name
- withdrawal.asset_is_known
- Type
- boolean
- Description
Whether the token is in the Fystack asset catalog. When
false,amount,value_usd, andasset_symbolare empty, so deny unknown tokens explicitly if you need to.
- Name
- withdrawal.token_address
- Type
- string
- Description
Token contract address. Empty for native coins.
- Name
- withdrawal.destination_address
- Type
- string
- Description
Recipient address.
- Name
- withdrawal.network_code
- Type
- string · ref network
- Description
Internal network code.
- Name
- withdrawal.is_whitelisted
- Type
- boolean
- Description
Whether the destination address is in any workspace address book entry.
- Name
- withdrawal.address_book_id
- Type
- string · ref address_book
- Description
The address book entry that contains the destination address. One entry covers all of its addresses, so an entry works as a named list.
- Name
- withdrawal.address_age_sec
- Type
- number
- Description
Seconds since the destination address was added to its address book entry. Use this for cooling-off rules on newly added recipients.
- Name
- withdrawal.destination_is_internal
- Type
- boolean
- Description
Whether the destination is another wallet in the same workspace.
- Name
- withdrawal.destination_wallet_id
- Type
- string · ref wallet
- Description
The destination wallet when the destination is internal.
Withdrawal velocity fields
Available on withdrawal.create only. All values are in USD unless noted.
- Name
- wallet.outflow_1h / outflow_6h / outflow_24h
- Type
- number
- Description
Realized outflow for the source wallet over the last 1, 6, or 24 hours.
- Name
- wallet.withdrawal_count_1h
- Type
- integer
- Description
Withdrawal attempts from the source wallet in the last hour.
- Name
- wallet.pending_outflow
- Type
- number
- Description
Value of withdrawals from the source wallet that are not executed yet.
- Name
- wallet.projected_outflow_24h
- Type
- number
- Description
Realized 24-hour outflow, plus pending outflow, plus the withdrawal being checked, for the source wallet.
- Name
- wallet.asset_projected_outflow_24h
- Type
- number
- Description
The same projection for the source wallet in units of the withdrawn asset, for example at most 5 BTC per day.
- Name
- workspace.outflow_1h / outflow_6h / outflow_24h
- Type
- number
- Description
Realized outflow for the workspace over the last 1, 6, or 24 hours.
- Name
- workspace.withdrawal_count_1h
- Type
- integer
- Description
Workspace withdrawal attempts in the last hour.
- Name
- workspace.pending_outflow
- Type
- number
- Description
Value of workspace withdrawals that are not executed yet.
- Name
- workspace.projected_outflow_24h
- Type
- number
- Description
Workspace projection: realized 24-hour outflow, plus pending outflow, plus the withdrawal being checked.
- Name
- workspace.network_outflow_1h / network_outflow_6h / network_outflow_24h
- Type
- number
- Description
Realized workspace outflow on the current network over the last 1, 6, or 24 hours.
- Name
- workspace.network_withdrawal_count_1h
- Type
- integer
- Description
Workspace withdrawal attempts on the current network in the last hour.
- Name
- workspace.network_pending_outflow
- Type
- number
- Description
Pending workspace outflow on the current network.
- Name
- workspace.network_projected_outflow_24h
- Type
- number
- Description
Workspace projection on the current network.
- Name
- workspace.initiator_projected_outflow_24h
- Type
- number
- Description
Projection for withdrawals created by the same member or API key, across the workspace.
Outflow and count fields are realized: they count a withdrawal only after it reaches an executed or successful terminal state. Pending outflow counts withdrawals that are waiting for approval, queued, or funding gas, whatever their age.
When an active rule uses a pending or projected field, Fystack evaluates the
workspace's withdrawals one at a time, so two concurrent requests cannot both
pass a limit. A request that waits longer than 5 seconds for its turn is
rejected with HTTP 429 and can be retried.
web3.contract_call fields
- Name
- resource.network_code
- Type
- string · ref network
- Description
Internal network code.
- Name
- resource.contract_address
- Type
- string
- Description
Lowercase contract address from the raw transaction
tofield.
- Name
- resource.method_selector
- Type
- string
- Description
Lowercase 4-byte selector, for example
0xa9059cbb.
- Name
- resource.method_name
- Type
- string
- Description
Method name from request metadata when provided.
- Name
- resource.value_wei
- Type
- string
- Description
Native value sent with the call, represented as a string.
- Name
- resource.gas_limit
- Type
- integer
- Description
Gas limit from the raw transaction.
- Name
- resource.decoded_args
- Type
- map
- Description
Decoded arguments when Fystack recognizes the call. ERC-20
transferexposestoandvalue.
web3.sign_message fields
- Name
- resource.method
- Type
- string
- Description
Signing method. One of
personal_sign,eth_sign, oreth_signTypedData_v4.
- Name
- resource.network_code
- Type
- string · ref network
- Description
Internal network code.
- Name
- resource.typed_data_primary_type
- Type
- string
- Description
EIP-712 primary type, for typed-data messages.
- Name
- resource.typed_data_domain_name
- Type
- string
- Description
EIP-712 domain name, for typed-data messages.
- Name
- resource.typed_data_verifying_contract
- Type
- string
- Description
EIP-712 verifying contract, for typed-data messages.
- Name
- resource.is_permit
- Type
- boolean
- Description
truefor EIP-2612, DAI, and Permit2 messages, which let the spender move tokens later without another signature.
- Name
- resource.permit_spender
- Type
- string
- Description
The address the permit authorizes.
- Name
- resource.permit_tokens
- Type
- string[]
- Description
Token contracts the permit covers. Use it with
in, for example"0x…" in resource.permit_tokens.
- Name
- resource.permit_amount_is_unlimited
- Type
- boolean
- Description
truefor max-value allowances (2159 or more, or a DAI permit withallowedset).
- Name
- resource.permit_deadline
- Type
- integer
- Description
Unix time the permit or its signature expires.
0when the message has none.
Approval lifecycle
Create, update, and delete policy operations are approval-gated. The mutation creates a policy approval request and returns status: "pending_approval" with an approval_request_id. The policy change is applied only after the approval group reaches threshold.
Rule create, update, and delete operations follow the same model when the parent policy has an approval group. The pending revision stores field-level diffs for audit and review.
Updating wallet targeting through /wallets applies immediately after validation and cache invalidation. Use the policy update endpoint when you want wallet targeting changes reviewed as part of a larger policy revision.
Authentication
Policy API endpoints accept either a signed API-key request or a bearer access token.
- Name
- ACCESS-API-KEY
- Type
- header
- Description
API key UUID for HMAC-authenticated requests.
- Name
- ACCESS-TIMESTAMP
- Type
- header
- Description
UNIX timestamp in seconds used to build the HMAC signature.
- Name
- ACCESS-SIGN
- Type
- header
- Description
Base64-encoded HMAC signature. See Authentication for the signing format.
- Name
- Authorization
- Type
- header
- Description
Bearer access token for user-session requests.
Policy approval decision endpoints remain user-session endpoints. Use bearer access tokens for approval request approve and reject actions.
Response envelope
Successful JSON responses use the standard Fystack envelope.
- Name
- success
- Type
- boolean
- Description
truewhen the request succeeds.
- Name
- message
- Type
- string
- Description
Response message, usually
success.
- Name
- code
- Type
- integer
- Description
Numeric result code. Success responses use
0.
- Name
- data
- Type
- object | array | null
- Description
Endpoint-specific response payload.
Validation errors use the same envelope with success: false and an errors array.
Data types
Policy
- Name
- id
- Type
- string
- Description
Policy UUID.
- Name
- created_at
- Type
- string
- Description
ISO timestamp when the policy was created.
- Name
- updated_at
- Type
- string
- Description
ISO timestamp when the policy was last updated.
- Name
- name
- Type
- string
- Description
Policy name. 3 to 100 characters.
- Name
- description
- Type
- string
- Description
Optional description. Up to 500 characters.
- Name
- status
- Type
- string
- Description
One of
pending,rejected,active, orinactive.
- Name
- version
- Type
- integer
- Description
Policy version number.
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- approval_group_id
- Type
- string | null
- Description
Approval group UUID assigned to the policy.
- Name
- wallet_ids
- Type
- string[]
- Description
Target wallet UUIDs. Empty array means the policy targets all wallets in the workspace.
- Name
- rules
- Type
- PolicyRule[]
- Description
Present on endpoints that preload rules.
- Name
- approval_group
- Type
- object
- Description
Present on endpoints that preload the approval group.
- Name
- pending_approval_request
- Type
- object
- Description
Present when there is a pending approval request for the policy.
PolicyRule
- Name
- id
- Type
- string
- Description
Rule UUID.
- Name
- policy_id
- Type
- string
- Description
Parent policy UUID.
- Name
- trigger_action
- Type
- string
- Description
One of
withdrawal.create,web3.transfer,web3.contract_call, orweb3.sign_message.
- Name
- condition
- Type
- string
- Description
Boolean condition expression. 1 to 4000 characters.
- Name
- effect
- Type
- string
- Description
One of
ALLOWorDENY.
- Name
- description
- Type
- string
- Description
Optional description. Up to 500 characters.
- Name
- is_enabled
- Type
- boolean
- Description
Whether the rule is enabled. Defaults to
trueon create when omitted.
Mutation response
Policy and rule mutation endpoints return a mutation payload in data.
- Name
- status
- Type
- string
- Description
One of
applied,pending_approval, ordeleted, depending on the operation.
- Name
- policy
- Type
- Policy
- Description
Returned by policy mutation endpoints when applicable.
- Name
- rule
- Type
- PolicyRule
- Description
Returned by rule mutation endpoints when applicable.
- Name
- approval_request_id
- Type
- string
- Description
Returned when the mutation is waiting for approval.
Get the policy schema
Returns fields a condition may reference, grouped by trigger_action and
section. Use this endpoint for condition builders and client-side previews.
Each field has a name and type. Optional keys:
- Name
- enum
- Type
- string[]
- Description
The allowed values for the field.
- Name
- ref
- Type
- string
- Description
The entity the field refers to. Render a picker for it. See Reference fields.
- Name
- deprecated
- Type
- boolean
- Description
Still evaluated for existing rules. Hide it from new-rule builders.
Request
curl -X GET https://api.fystack.io/api/v1/policy/schema \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Response
200{
"success": true,
"message": "success",
"code": 0,
"data": {
"withdrawal.create": {
"principal": [
{ "name": "user_email", "type": "string", "deprecated": true },
{ "name": "user_id", "type": "string", "ref": "workspace_user" },
{ "name": "role", "type": "string", "enum": ["owner", "admin", "signer", "proposer", "viewer"] },
{ "name": "initiator_type", "type": "string", "enum": ["user", "api_key"] },
{ "name": "api_key_id", "type": "string", "ref": "api_key" }
],
"withdrawal": [
{ "name": "amount", "type": "number" },
{ "name": "value_usd", "type": "number" },
{ "name": "asset_symbol", "type": "string", "ref": "asset_symbol" },
{ "name": "asset_is_native", "type": "boolean" },
{ "name": "asset_is_known", "type": "boolean" },
{ "name": "token_address", "type": "string" },
{ "name": "destination_address", "type": "string" },
{ "name": "network_code", "type": "string", "ref": "network" },
{ "name": "is_whitelisted", "type": "boolean" },
{ "name": "address_age_sec", "type": "number" },
{ "name": "address_book_id", "type": "string", "ref": "address_book" },
{ "name": "destination_is_internal", "type": "boolean" },
{ "name": "destination_wallet_id", "type": "string", "ref": "wallet" }
],
"wallet": [
{ "name": "id", "type": "string", "ref": "wallet" },
{ "name": "purpose", "type": "string", "enum": ["general", "custody", "payment", "user", "one_time_use", "gas_tank", "deployment"] },
{ "name": "type", "type": "string", "enum": ["standard", "mpc"] },
{ "name": "outflow_1h", "type": "number" },
{ "name": "outflow_6h", "type": "number" },
{ "name": "outflow_24h", "type": "number" },
{ "name": "withdrawal_count_1h", "type": "integer" },
{ "name": "pending_outflow", "type": "number" },
{ "name": "projected_outflow_24h", "type": "number" },
{ "name": "asset_projected_outflow_24h", "type": "number" }
],
"workspace": [
{ "name": "outflow_1h", "type": "number" },
{ "name": "outflow_6h", "type": "number" },
{ "name": "outflow_24h", "type": "number" },
{ "name": "withdrawal_count_1h", "type": "integer" },
{ "name": "network_outflow_1h", "type": "number" },
{ "name": "network_outflow_6h", "type": "number" },
{ "name": "network_outflow_24h", "type": "number" },
{ "name": "network_withdrawal_count_1h", "type": "integer" },
{ "name": "pending_outflow", "type": "number" },
{ "name": "projected_outflow_24h", "type": "number" },
{ "name": "network_pending_outflow", "type": "number" },
{ "name": "network_projected_outflow_24h", "type": "number" },
{ "name": "initiator_projected_outflow_24h", "type": "number" }
],
"context": [
{ "name": "hour", "type": "integer" },
{ "name": "day_of_week", "type": "string" }
]
},
"web3.transfer": {
"principal": [ "... same as withdrawal.create ..." ],
"withdrawal": [ "... same as withdrawal.create ..." ],
"wallet": [
{ "name": "id", "type": "string", "ref": "wallet" },
{ "name": "purpose", "type": "string", "enum": ["general", "custody", "payment", "user", "one_time_use", "gas_tank", "deployment"] },
{ "name": "type", "type": "string", "enum": ["standard", "mpc"] }
],
"context": [ "... same as withdrawal.create ..." ]
},
"web3.contract_call": {
"principal": [ "... same as withdrawal.create ..." ],
"resource": [
{ "name": "network_code", "type": "string", "ref": "network" },
{ "name": "contract_address", "type": "string" },
{ "name": "method_selector", "type": "string" },
{ "name": "method_name", "type": "string" },
{ "name": "value_wei", "type": "string" },
{ "name": "gas_limit", "type": "integer" },
{ "name": "decoded_args", "type": "map" }
],
"wallet": [ "... id, purpose, type ..." ],
"context": [ "... same as withdrawal.create ..." ]
},
"web3.sign_message": {
"principal": [ "... same as withdrawal.create ..." ],
"resource": [
{ "name": "method", "type": "string", "enum": ["personal_sign", "eth_sign", "eth_signTypedData_v4"] },
{ "name": "network_code", "type": "string", "ref": "network" },
{ "name": "typed_data_primary_type", "type": "string" },
{ "name": "typed_data_domain_name", "type": "string" },
{ "name": "typed_data_verifying_contract", "type": "string" },
{ "name": "is_permit", "type": "boolean" },
{ "name": "permit_spender", "type": "string" },
{ "name": "permit_tokens", "type": "string[]" },
{ "name": "permit_amount_is_unlimited", "type": "boolean" },
{ "name": "permit_deadline", "type": "integer" }
],
"wallet": [ "... id, purpose, type ..." ],
"context": [ "... same as withdrawal.create ..." ]
}
}
}
Validate a rule
Validates a rule body without creating or updating a rule.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
Body parameters
- Name
- trigger_action
- Type
- string
- Required
- required
- Description
One of
withdrawal.create,web3.transfer,web3.contract_call, orweb3.sign_message.
- Name
- condition
- Type
- string
- Required
- required
- Description
1 to 4000 characters. Must compile to a boolean expression.
- Name
- effect
- Type
- string
- Required
- required
- Description
One of
ALLOWorDENY.web3.sign_messagerules are deny only.
Response data
- Name
- valid
- Type
- boolean
- Description
truewhen the rule body validates.
Request
curl -X POST https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/rules/validate \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{
"trigger_action": "withdrawal.create",
"condition": "withdrawal.value_usd > 10000",
"effect": "DENY"
}'
Response
200{
"success": true,
"message": "success",
"code": 0,
"data": { "valid": true }
}
Create a policy
Creates a policy mutation.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
Body parameters
- Name
- name
- Type
- string
- Required
- required
- Description
3 to 100 characters.
- Name
- description
- Type
- string
- Description
Optional. Up to 500 characters.
- Name
- approval_group_id
- Type
- string
- Required
- required
- Description
Approval group UUID.
- Name
- wallet_ids
- Type
- string[]
- Description
Optional. Up to 500 wallet UUIDs.
- Name
- rules
- Type
- PolicyRuleCreate[]
- Description
Optional. Up to 50 rules.
Request
curl -X POST https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{
"name": "Large withdrawal guard",
"approval_group_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"wallet_ids": [],
"rules": [
{
"trigger_action": "withdrawal.create",
"condition": "withdrawal.value_usd > 10000",
"effect": "DENY"
}
]
}'
Response
200{
"success": true,
"message": "success",
"code": 0,
"data": {
"status": "pending_approval",
"approval_request_id": "b7e2a1c4-9d8f-4a3b-8c1d-2e3f4a5b6c7d",
"policy": {
"id": "9c8b7a6d-5e4f-3210-9a8b-7c6d5e4f3210",
"name": "Large withdrawal guard",
"status": "pending",
"version": 1,
"wallet_ids": []
}
}
}
List policies
Returns policies in a workspace.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
Query parameters
- Name
- status
- Type
- string
- Description
Optional. One of
pending,rejected,active, orinactive.
- Name
- limit
- Type
- integer
- Description
Optional. Maximum
1000.
- Name
- offset
- Type
- integer
- Description
Optional. Maximum
100000.
Response data
- Name
- data
- Type
- Policy[]
- Description
Array of policy objects.
Request
curl -X GET "https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies?status=active&limit=20&offset=0" \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Get a policy
Returns a single policy.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Response data
- Name
- data
- Type
- Policy
- Description
Policy object, including rules, approval group, and pending approval request when present.
Request
curl -X GET https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id} \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Update a policy
Updates policy fields.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Body parameters
- Name
- name
- Type
- string
- Description
Optional. 3 to 100 characters.
- Name
- description
- Type
- string
- Description
Optional. Up to 500 characters.
- Name
- approval_group_id
- Type
- string
- Description
Optional approval group UUID.
- Name
- wallet_ids
- Type
- string[]
- Description
Optional. Up to 500 wallet UUIDs. Send an empty array to target all wallets.
- Name
- rules
- Type
- PolicyRuleCreate[]
- Description
Optional. Replaces the complete rule set when present. Send an empty array to clear all rules.
Request
curl -X PUT https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id} \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated policy description",
"wallet_ids": ["e0851607-417a-475a-b399-5f3aa262b81d"]
}'
Delete a policy
Deletes a policy or creates a delete mutation that waits for approval.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Response data
- Name
- data
- Type
- Mutation response
- Description
Policy mutation response.
Request
curl -X DELETE https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id} \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Get policy history
Returns policy revisions for one policy.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Response data
- Name
- data
- Type
- PolicyRevision[]
- Description
Array of policy revisions, including field-level
changeswhen present.
Request
curl -X GET https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id}/history \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Get policy wallets
Returns the policy with its current wallet_ids.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Response data
- Name
- data
- Type
- Policy
- Description
Policy object.
Request
curl -X GET https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id}/wallets \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Update policy wallets
Replaces a policy's wallet targeting list.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Body parameters
- Name
- wallet_ids
- Type
- string[]
- Description
Optional. Up to 500 wallet UUIDs. Send an empty array to target all wallets.
Request
curl -X PUT https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id}/wallets \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{ "wallet_ids": ["e0851607-417a-475a-b399-5f3aa262b81d"] }'
Create a policy rule
Creates one rule under an existing policy.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- policy_id
- Type
- string
- Description
Policy UUID.
Body parameters
- Name
- id
- Type
- string
- Description
Optional rule UUID.
- Name
- trigger_action
- Type
- string
- Required
- required
- Description
One of
withdrawal.create,web3.transfer,web3.contract_call, orweb3.sign_message.
- Name
- condition
- Type
- string
- Required
- required
- Description
1 to 4000 characters.
- Name
- effect
- Type
- string
- Required
- required
- Description
One of
ALLOWorDENY.web3.sign_messagerules are deny only.
- Name
- description
- Type
- string
- Description
Optional. Up to 500 characters.
- Name
- is_enabled
- Type
- boolean
- Description
Optional. Defaults to
true.
Request
curl -X POST https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/{policy_id}/rules \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{
"trigger_action": "web3.contract_call",
"condition": "resource.gas_limit > 100000",
"effect": "DENY",
"is_enabled": true
}'
Update a policy rule
Updates mutable rule fields. The trigger action is immutable; delete and recreate the rule to change it.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- rule_id
- Type
- string
- Description
Rule UUID.
Body parameters
- Name
- condition
- Type
- string
- Description
Optional. 1 to 4000 characters.
- Name
- effect
- Type
- string
- Description
Optional. One of
ALLOWorDENY.
- Name
- description
- Type
- string
- Description
Optional. Up to 500 characters.
- Name
- is_enabled
- Type
- boolean
- Description
Optional.
Request
curl -X PUT https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/rules/{rule_id} \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature==" \
-H "Content-Type: application/json" \
-d '{
"condition": "resource.gas_limit > 150000",
"is_enabled": true
}'
Delete a policy rule
Deletes one policy rule or creates a rule deletion mutation that waits for approval.
Path parameters
- Name
- workspace_id
- Type
- string
- Description
Workspace UUID.
- Name
- rule_id
- Type
- string
- Description
Rule UUID.
Response data
- Name
- data
- Type
- Mutation response
- Description
Rule mutation response.
Request
curl -X DELETE https://api.fystack.io/api/v1/workspaces/{workspace_id}/policies/rules/{rule_id} \
-H "ACCESS-API-KEY: your_api_key" \
-H "ACCESS-TIMESTAMP: 1667836889" \
-H "ACCESS-SIGN: YourBase64EncodedSignature=="
Design patterns
Use DENY rules for hard controls: sanctions lists, unknown recipients, new address book entries, projected outflow limits, unknown tokens, unlimited permits, or high-risk contract methods.
Use ALLOW rules narrowly. A matching ALLOW skips approval, so conditions should be precise: low USD value, trusted initiator, known wallet target, known network, and a known recipient with an address age check, or a known contract and method.
Pair every ALLOW with a DENY ceiling, such as wallet.projected_outflow_24h or workspace.initiator_projected_outflow_24h. Because DENY always wins, the ceiling also caps the auto-approved path.
Keep policy sets small and explicit. Broad workspace-wide policies are useful for baseline controls; wallet-scoped policies are better when a hot wallet, treasury wallet, or customer wallet has different risk thresholds.
See Policy Templates for complete examples.