Skip to main content

State Controller Management

Reference documentation for the State Controller Management API endpoints.

If you are new to the feature, start with Stateful Policy Onboarding: Duo, Trio, Silent Network.

Endpoint summary

OperationEndpointPurpose
Get controllersPOST /v2/rest/getStateControllersList controllers and current entries for a key
Create controllerPOST /v2/rest/createStateControllerCreate one controller for future policy state binding
Delete controllerPOST /v2/rest/deleteStateControllerRemove one controller and its associated state

Common request envelope

All endpoints use this envelope:

{
"payload": { "...": "..." },
"userSignatures": { ... }
}

userSignatures follows the same signed-auth pattern as policy management APIs.

1) Get state controllers

Request

POST /v2/rest/getStateControllers

{
"payload": {
"key_id": "alice_key_id"
},
"userSignatures": { ... }
}

Response

{
"controllers": [
{
"id": "ctrl_123",
"key_id": "alice_key_id",
"description": "Daily rolling spend limit",
"method": "sum",
"window_config": {
"type": "rolling",
"seconds": 86400
},
"partition_by": [
{
"transaction_type": "erc20",
"transaction_attr": "to"
}
],
"referenced_by": "0xabc...",
"entries": [
{
"controller_id": "ctrl_123",
"partition_key": "0x...",
"value": "650"
}
]
}
]
}

Notes

  • For a new key, controllers will be empty.
  • Right after creating a controller (before sign traffic), entries is typically empty.
  • referenced_by is null until the controller is bound by a policy condition using state.id.

2) Create state controller

Request

POST /v2/rest/createStateController

{
"payload": {
"key_id": "alice_key_id",
"description": "Daily rolling spend limit",
"method": "sum",
"window": {
"type": "rolling",
"seconds": 86400
},
"partition_by": [
{
"transaction_type": "erc20",
"transaction_attr": "to"
}
]
},
"userSignatures": { ... }
}

Payload fields

FieldTypeRequiredDescription
key_idstringYesKey the controller belongs to
descriptionstringNoHuman-readable label
method"sum" | "count"YesAggregation method
windowobjectYesWindow config: rolling or fixed
partition_byarrayYesNon-empty partition field list

window options:

  • Rolling: { "type": "rolling", "seconds": <number> }
  • Fixed: { "type": "fixed", "interval": "day" | "week" | "month" }

interval behavior:

  • day resets at 00:00:00 UTC each calendar day.
  • week resets every Monday at 00:00:00 UTC, with window_start anchored to the Monday of the current week once a partition is evaluated.
  • month resets at the first day of the next month; the entry records window_start as the first day of the current month at 00:00 UTC when it is evaluated.

Response

{
"id": "ctrl_123",
"key_id": "alice_key_id",
"description": "Daily rolling spend limit",
"method": "sum",
"window_config": {
"type": "rolling",
"seconds": 86400
},
"partition_by": [
{
"transaction_type": "erc20",
"transaction_attr": "to"
}
],
"referenced_by": null
}

Notes

  • Each key can have up to 20 controllers.
  • Create controller first, then update policy to reference state.id.
  • Changing semantics (method, window, partition_by, or bound condition fields) without recreating the controller leaves the linked condition invalid and every evaluation returns deny.

3) Delete state controller

Request

POST /v2/rest/deleteStateController

{
"payload": {
"key_id": "alice_key_id",
"controller_id": "ctrl_123"
},
"userSignatures": { ... }
}

Response

{
"status": "success"
}

Notes

  • Deleting a controller removes its associated state.
  • If policy still references a deleted state.id, that stateful condition will fail evaluation. To restore the condition, either create a new controller or remove the state.id reference from the policy.

Integration flow

  1. Create controller (createStateController).
  2. Insert returned id into policy condition as state.id.
  3. Update policy.
  4. Sign traffic to populate entries over time.
  5. Query controllers (getStateControllers) to inspect entries.