Delegated signers API
Owner-facing HTTP surface for non-OAuth (bring-your-own key) provisioning
on a spend account. Mounted on the gateway under /v1/delegated-signers.
All routes require a verified owner session via ownerFromRequest (wallet
personal-message session or trusted internal hop). Bare owner headers are
rejected.
The owner session authenticates who may call prepare/finalize. Only an owner-signed Move transaction creates, pauses, resumes, or revokes grants on chain.
Policy scope fields (create)
Per asset in assetScopes:
| Field | Type | Meaning |
|---|---|---|
asset | string | Fully qualified coin type |
totalBudgetAtomic | decimal string | Grant session / total budget (atomic units) → on-chain session_cap |
maxPerPaymentAtomic | decimal string | Per-signer per-payment ceiling → on-chain max_per_payment |
expiresAt | ISO-8601, ms epoch, or omit/null | Wall-clock expiry; omit for no expiry (0 on chain) |
Also on create:
| Field | Meaning |
|---|---|
agentAddress | Agent public Sui address only |
name | Human label for the logical signer |
profileIds | Optional profiles (resolved to policy ids before tx build) |
policyIds | Optional off-chain policy row ids |
idempotencyKey | Required; scopes the whole multi-asset aggregate |
Aggregate status
| Status | Meaning |
|---|---|
awaiting_owner_signature | Tx kinds prepared; owner must sign |
partially_active | Some but not all per-asset grants finalized |
active | Every expected GrantCreated verified |
revoking / partially_revoked / revoked | Terminal revoke path |
expired / failed | Terminal non-spend / error |
pausing / paused | Reversible pause path (GrantPauseSet) |
Per-asset grant status: pending | active | pausing | paused | revoking | revoked | failed.
POST /v1/delegated-signers
Create or idempotently replay a multi-asset delegated signer.
Request body
{
"agentAddress": "0x...",
"name": "agent-name",
"profileIds": ["..."],
"policyIds": ["..."],
"assetScopes": [
{
"asset": "0x...::usdc::USDC",
"totalBudgetAtomic": "10000000",
"maxPerPaymentAtomic": "250000",
"expiresAt": "2026-12-31T00:00:00.000Z"
}
],
"idempotencyKey": "unique-key"
}Success response
{
"status": "awaiting_owner_signature",
"delegatedSignerId": "ds_...",
"agentAddress": "0x...",
"name": "agent-name",
"method": "delegated_key",
"idempotentReplay": false,
"grants": [
{
"grantId": "g_...",
"asset": "0x...::usdc::USDC",
"coinType": "0x...::usdc::USDC",
"pool": "0x...",
"policyIds": ["..."],
"onChainPolicyIds": [1],
"caps": {
"sessionCap": "10000000",
"maxPerPayment": "250000"
},
"expiry": "1798675200000",
"moveTarget": "0x...::spend_account::create_policy_grant",
"txKindBytes": "<base64>",
"sender": "0xOWNER..."
}
]
}Owner signs each txKindBytes, executes, then finalizes.
POST /v1/delegated-signers/:id/grants/:grantId/finalize
Finalize one per-asset create after owner-signed create_grant.
Request
{ "digest": "..." }Success
{
"delegatedSignerId": "ds_...",
"grantId": "g_...",
"asset": "0x...::usdc::USDC",
"grantObjectId": "0xGRANT...",
"alreadyFinalized": false,
"status": "active"
}Activates only when GrantCreated verifies (including max_per_payment).
GET /v1/delegated-signers
List logical signers for the authenticated owner.
{
"delegatedSigners": [
{
"id": "ds_...",
"name": "agent-name",
"agentAddress": "0x...",
"method": "delegated_key",
"status": "active",
"profileIds": [],
"policyIds": [],
"grants": [
{
"id": "g_...",
"asset": "0x...::usdc::USDC",
"coinType": "0x...::usdc::USDC",
"pool": "0x...",
"grantObjectId": "0x...",
"status": "active",
"policyIds": [],
"onChainPolicyIds": [1],
"caps": {
"sessionCap": "10000000",
"maxPerPayment": "250000"
},
"expiry": "1798675200000"
}
],
"createdAt": "...",
"updatedAt": "..."
}
]
}GET /v1/delegated-signers/:id
Fetch one aggregate. Response: { "delegatedSigner": { ... } } (same public
shape as list items).
DELETE /v1/delegated-signers/:id
Prepare terminal revoke: one revoke_grant tx-kind per active grant.
{
"status": "revoking",
"delegatedSignerId": "ds_...",
"grants": [
{
"grantId": "g_...",
"asset": "0x...::usdc::USDC",
"grantObjectId": "0x...",
"moveTarget": "0x...::spend_account::revoke_grant",
"txKindBytes": "<base64>",
"sender": "0xOWNER..."
}
]
}POST /v1/delegated-signers/:id/grants/:grantId/revoke/finalize
Finalize one per-asset revoke after owner-signed revoke_grant. Marks
revoked only when GrantRevoked verifies for the expected grant id.
{ "digest": "..." }Response shape mirrors create finalize (alreadyFinalized, aggregate
status, etc.).
POST /v1/delegated-signers/:id/pause/prepare
Prepare owner-signed pause: one pause_grant / pause_spend_grant /
pause_open_grant tx-kind per active or in-flight grant. Response shape
matches revoke prepare.
POST /v1/delegated-signers/:id/grants/:grantId/pause/finalize
Finalize one per-asset pause after the owner-signed pause. Marks paused
only when GrantPauseSet verifies for the expected grant id with
paused=true.
{ "digest": "..." }POST /v1/delegated-signers/:id/resume/prepare
Prepare owner-signed resume: one resume_* tx-kind per paused grant.
POST /v1/delegated-signers/:id/grants/:grantId/resume/finalize
Finalize one per-asset resume after the owner-signed resume. Marks active
only when GrantPauseSet verifies paused=false.
A paused signer cannot pay. Resume restores pay under the existing caps. Only the pool owner can pause or resume.
Error body
{
"ok": false,
"code": "INVALID",
"message": "...",
"reason": "..."
}| Code | Typical HTTP |
|---|---|
UNAUTHENTICATED | 401 |
NOT_FOUND | 404 |
CONFLICT / NOT_FINAL | 409 |
INVALID / UNSUPPORTED_ASSET | 400 |
UNAVAILABLE | 503 |
TX_FAILED / FOREIGN_DIGEST / UNVERIFIED | 502 |