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 DENY blocks the request. A matching ALLOW bypasses 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 as withdrawal.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 as web3.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 DENY records the activity as denied_by_policy. A matching ALLOW skips 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, or eth_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, optional description, and optional is_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:

  1. If any matching rule has effect: "DENY", the action is blocked.
  2. Otherwise, if any matching rule has effect: "ALLOW", the action proceeds and transaction approval is bypassed.
  3. 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, or viewer.

  • Name
    principal.initiator_type
    Type
    string
    Description

    user for a web session, or api_key for 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_id instead, 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, or deployment.

  • Name
    wallet.type
    Type
    string
    Description

    Source wallet type. standard or mpc.

  • Name
    context.hour
    Type
    integer
    Description

    UTC hour of evaluation, 0 through 23.

  • Name
    context.day_of_week
    Type
    string
    Description

    UTC weekday name, for example Monday or Saturday.

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, or BTC.

  • 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, and asset_symbol are 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 to field.

  • 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 transfer exposes to and value.

web3.sign_message fields

  • Name
    resource.method
    Type
    string
    Description

    Signing method. One of personal_sign, eth_sign, or eth_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

    true for 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

    true for max-value allowances (2159 or more, or a DAI permit with allowed set).

  • Name
    resource.permit_deadline
    Type
    integer
    Description

    Unix time the permit or its signature expires. 0 when 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

    true when 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, or inactive.

  • 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, or web3.sign_message.

  • Name
    condition
    Type
    string
    Description

    Boolean condition expression. 1 to 4000 characters.

  • Name
    effect
    Type
    string
    Description

    One of ALLOW or DENY.

  • Name
    description
    Type
    string
    Description

    Optional description. Up to 500 characters.

  • Name
    is_enabled
    Type
    boolean
    Description

    Whether the rule is enabled. Defaults to true on 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, or deleted, 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

GET
/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

GET
/policy/schema
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

POST
/workspaces/{workspace_id}/policies/rules/validate

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, or web3.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 ALLOW or DENY. web3.sign_message rules are deny only.

Response data

  • Name
    valid
    Type
    boolean
    Description

    true when the rule body validates.

Request

POST
/workspaces/{workspace_id}/policies/rules/validate
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

POST
/workspaces/{workspace_id}/policies

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

POST
/workspaces/{workspace_id}/policies
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

GET
/workspaces/{workspace_id}/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, or inactive.

  • 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

GET
/workspaces/{workspace_id}/policies
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

GET
/workspaces/{workspace_id}/policies/{policy_id}

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

GET
/workspaces/{workspace_id}/policies/{policy_id}
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

PUT
/workspaces/{workspace_id}/policies/{policy_id}

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

PUT
/workspaces/{workspace_id}/policies/{policy_id}
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

DELETE
/workspaces/{workspace_id}/policies/{policy_id}

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

DELETE
/workspaces/{workspace_id}/policies/{policy_id}
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

GET
/workspaces/{workspace_id}/policies/{policy_id}/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 changes when present.

Request

GET
/workspaces/{workspace_id}/policies/{policy_id}/history
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

GET
/workspaces/{workspace_id}/policies/{policy_id}/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

GET
/workspaces/{workspace_id}/policies/{policy_id}/wallets
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

PUT
/workspaces/{workspace_id}/policies/{policy_id}/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

PUT
/workspaces/{workspace_id}/policies/{policy_id}/wallets
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

POST
/workspaces/{workspace_id}/policies/{policy_id}/rules

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, or web3.sign_message.

  • Name
    condition
    Type
    string
    Required
    required
    Description

    1 to 4000 characters.

  • Name
    effect
    Type
    string
    Required
    required
    Description

    One of ALLOW or DENY. web3.sign_message rules are deny only.

  • Name
    description
    Type
    string
    Description

    Optional. Up to 500 characters.

  • Name
    is_enabled
    Type
    boolean
    Description

    Optional. Defaults to true.

Request

POST
/workspaces/{workspace_id}/policies/{policy_id}/rules
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

PUT
/workspaces/{workspace_id}/policies/rules/{rule_id}

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 ALLOW or DENY.

  • Name
    description
    Type
    string
    Description

    Optional. Up to 500 characters.

  • Name
    is_enabled
    Type
    boolean
    Description

    Optional.

Request

PUT
/workspaces/{workspace_id}/policies/rules/{rule_id}
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

DELETE
/workspaces/{workspace_id}/policies/rules/{rule_id}

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

DELETE
/workspaces/{workspace_id}/policies/rules/{rule_id}
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.