Policies

Programmable policies codify guardrails for sensitive actions. Each policy holds rules, written as expression conditions, that are evaluated automatically when a withdrawal is requested or a smart contract is called. A matching rule can block the action, let it bypass the approval gate, or leave it to your normal approval flow.

At evaluation time, DENY wins: any matching DENY blocks the action. A matching ALLOW with no DENY bypasses the approval gate. No match falls through to the normal approval flow. Evaluation is fail-closed: a compile error, timeout, or infrastructure failure denies the action.

Like withdrawals, every policy and rule change goes through manual approval before it takes effect. Each policy is tied to an approval group, so a mutating endpoint (create, update, delete) does not apply the change directly: it returns status: "pending_approval" with an approval_request_id, and the change waits on the policy's approval group to sign off.


The policy model

A policy is the parent container for a set of rules. It is always gated behind an approval group, and can optionally be scoped to specific wallets.

  • Name
    id
    Type
    string
    Description

    The UUID of the policy.

  • Name
    name
    Type
    string
    Description

    Policy name, 3 to 100 characters.

  • Name
    status
    Type
    string
    Description

    One of pending (awaiting approval), active (live), inactive (disabled), or rejected.

  • Name
    version
    Type
    integer
    Description

    Version number, incremented on each applied change.

  • Name
    workspace_id
    Type
    string
    Description

    The UUID of the owning workspace.

  • Name
    approval_group_id
    Type
    string
    Description

    The approval group UUID that gates changes to this policy. Every change is gated behind an approval request.

  • Name
    wallet_ids
    Type
    array
    Description

    Wallet UUIDs this policy targets. Empty means all wallets in the workspace.

  • Name
    rules
    Type
    array
    Description

    The rules belonging to this policy. See the rule model below.

The rule model

  • Name
    id
    Type
    string
    Description

    The UUID of the rule.

  • Name
    trigger_action
    Type
    string
    Description

    The action this rule evaluates: withdrawal.create or web3.contract_call. Immutable once created.

  • Name
    condition
    Type
    string
    Description

    The expression evaluated against the action payload, 1 to 4000 characters. See Writing conditions.

  • Name
    effect
    Type
    string
    Description

    DENY (block, always wins) or ALLOW (bypass the approval gate).

  • Name
    is_enabled
    Type
    boolean
    Description

    Whether the rule participates in evaluation. Defaults to true.


GET/policy/schema

Get the policy schema

Return the fields a condition can reference, grouped per trigger action. The response is keyed by trigger action, each exposing sections (principal, resource, withdrawal, wallet, workspace, context) whose fields carry a name, type, and optional enum.

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

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": {
    "withdrawal.create": {
      "principal": [
        { "name": "user_email", "type": "string" },
        { "name": "role", "type": "string", "enum": ["owner", "admin", "signer", "proposer", "viewer", "guest"] }
      ],
      "withdrawal": [
        { "name": "amount", "type": "number" },
        { "name": "value_usd", "type": "number" },
        { "name": "asset_symbol", "type": "string" },
        { "name": "is_whitelisted", "type": "boolean" },
        { "name": "destination_address", "type": "string" },
        { "name": "network_code", "type": "string" }
      ],
      "wallet": [{ "name": "outflow_24h", "type": "number" }],
      "workspace": [{ "name": "outflow_24h", "type": "number" }],
      "context": [
        { "name": "hour", "type": "integer" },
        { "name": "day_of_week", "type": "string" }
      ]
    },
    "web3.contract_call": {
      "principal": [
        { "name": "user_email", "type": "string" },
        { "name": "role", "type": "string", "enum": ["owner", "admin", "signer", "proposer", "viewer", "guest"] }
      ],
      "resource": [
        { "name": "network_code", "type": "string" },
        { "name": "contract_address", "type": "string" },
        { "name": "method_name", "type": "string" },
        { "name": "value_wei", "type": "string" },
        { "name": "decoded_args", "type": "map" }
      ],
      "context": [
        { "name": "hour", "type": "integer" },
        { "name": "day_of_week", "type": "string" }
      ]
    }
  }
}

POST/workspaces/{workspace_id}/policies

Create a policy

Create a policy in a workspace. Rules can be included inline or added later.

Body parameters

  • Name
    name
    Type
    string
    Description

    Required. 3 to 100 characters.

  • Name
    description
    Type
    string
    Description

    Optional, up to 500 characters.

  • Name
    approval_group_id
    Type
    string
    Description

    Required. The approval group UUID that must sign off on changes to this policy.

  • Name
    wallet_ids
    Type
    array
    Description

    Optional wallet UUIDs to target, up to 500. Empty applies to all wallets.

  • Name
    rules
    Type
    array
    Description

    Optional rules to create with the policy, up to 50. Each entry takes trigger_action, condition, effect, and optional description and is_enabled.

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 && !withdrawal.is_whitelisted",
        "effect": "DENY"
      }
    ]
  }'

Response

{
  "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,
      "approval_group_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "wallet_ids": [],
      "rules": []
    }
  }
}

GET/workspaces/{workspace_id}/policies

List policies

Retrieve all policies in a workspace, with their rules.

Query parameters

  • Name
    status
    Type
    string
    Description

    Optional filter: pending, rejected, active, or inactive.

  • Name
    offset
    Type
    integer
    Description

    Optional. Records to skip.

  • Name
    limit
    Type
    integer
    Description

    Optional. Maximum records to return.

Request

GET
/workspaces/{workspace_id}/policies
curl -G 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==" \
  -d status=active

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": [
    {
      "id": "9c8b7a6d-5e4f-3210-9a8b-7c6d5e4f3210",
      "name": "Large withdrawal guard",
      "status": "active",
      "version": 1,
      "approval_group_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "wallet_ids": [],
      "rules": [
        {
          "id": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
          "trigger_action": "withdrawal.create",
          "condition": "withdrawal.value_usd > 10000 && !withdrawal.is_whitelisted",
          "effect": "DENY",
          "is_enabled": true
        }
      ]
    }
  ]
}

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

Get a policy

Retrieve a single policy and its rules.

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=="

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

Update a policy

Update a policy's metadata, wallet targeting, or rule set. All body fields are optional; only the fields you send change.

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
    array
    Description

    Optional. Replaces the targeted wallet set, up to 500 UUIDs.

  • Name
    rules
    Type
    array
    Description

    Optional. Replaces the entire rule set. An empty array clears all rules; omit to leave rules untouched.

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 guard for large outflows",
    "wallet_ids": ["e0851607-417a-475a-b399-5f3aa262b81d"]
  }'

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": {
    "status": "pending_approval",
    "approval_request_id": "b7e2a1c4-9d8f-4a3b-8c1d-2e3f4a5b6c7d"
  }
}

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

Delete a policy

Delete a policy and its rules.

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=="

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": { "status": "applied" }
}

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

Get policy history

Retrieve the revision history of a policy. Each revision records the action, approval status, changed_by, and field-level changes (each with target_type, field, old_value, new_value).

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=="

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": [
    {
      "version": 2,
      "action": "edit",
      "status": "approved",
      "change_summary": "Updated description and wallet targeting",
      "changed_by": "075243c1-962e-4803-83da-d73c78b8b060",
      "changes": [
        {
          "target_type": "policy",
          "field": "description",
          "old_value": { "value": "Deny large outflows" },
          "new_value": { "value": "Updated guard for large outflows" }
        }
      ]
    }
  ]
}

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

Get policy wallets

Retrieve a policy's wallet targeting. An empty wallet_ids array means all wallets in the workspace.

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=="

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

Update policy wallets

Replace the wallets a policy targets. Send an empty array to apply to all wallets.

Body parameters

  • Name
    wallet_ids
    Type
    array
    Description

    Wallet UUIDs to target, up to 500. Empty targets 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"] }'

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

Create a rule

Add a rule to an existing policy.

Body parameters

  • Name
    trigger_action
    Type
    string
    Description

    Required. withdrawal.create or web3.contract_call.

  • Name
    condition
    Type
    string
    Description

    Required. 1 to 4000 characters.

  • Name
    effect
    Type
    string
    Description

    Required. ALLOW or DENY.

  • 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": "withdrawal.create",
    "condition": "principal.role == \"proposer\" && withdrawal.value_usd > 1000",
    "effect": "DENY"
  }'

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": {
    "status": "applied",
    "rule": {
      "id": "2b3c4d5e-6f70-8192-a3b4-c5d6e7f80910",
      "policy_id": "9c8b7a6d-5e4f-3210-9a8b-7c6d5e4f3210",
      "trigger_action": "withdrawal.create",
      "condition": "principal.role == \"proposer\" && withdrawal.value_usd > 1000",
      "effect": "DENY",
      "is_enabled": true
    }
  }
}

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

Update a rule

Update a rule's condition, effect, description, or is_enabled. The trigger action is immutable: delete and recreate to change it.

Body parameters

  • Name
    condition
    Type
    string
    Description

    Optional. 1 to 4000 characters.

  • Name
    effect
    Type
    string
    Description

    Optional. ALLOW or DENY.

  • Name
    description
    Type
    string
    Description

    Optional, up to 500 characters.

  • Name
    is_enabled
    Type
    boolean
    Description

    Optional. Enable or disable the rule.

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 '{ "is_enabled": false }'

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

Delete a rule

Delete a rule from its policy.

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=="

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

Validate a rule

Dry-run a condition without saving. It is compiled against the schema for its trigger action; a compile error is returned on failure. Body takes trigger_action, condition, and effect.

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 && !withdrawal.is_whitelisted",
    "effect": "DENY"
  }'

Response

{
  "success": true,
  "message": "success",
  "code": 0,
  "data": { "valid": true }
}

Writing conditions

Conditions are expressions that return a boolean. Reference fields by section and name (e.g. withdrawal.value_usd, principal.role), combine with &&, ||, !, and compare with ==, !=, >, >=, <, <=. Call Get the policy schema for the fields available per trigger action.

  • Name
    Block large unwhitelisted transfers
    Type
    DENY
    Description

    withdrawal.value_usd > 10000 && !withdrawal.is_whitelisted

  • Name
    Restrict a role
    Type
    DENY
    Description

    principal.role == "proposer" && withdrawal.value_usd > 1000

  • Name
    Guard decoded contract arguments
    Type
    DENY
    Description

    has(resource.decoded_args.to) && resource.decoded_args.to == "0x000000000000000000000000000000000000dead"

ABI decoding can fail, so guard any access to resource.decoded_args with a has(...) check before reading a key.