Skip to content
LogoLogo

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:

FieldTypeMeaning
assetstringFully qualified coin type
totalBudgetAtomicdecimal stringGrant session / total budget (atomic units) → on-chain session_cap
maxPerPaymentAtomicdecimal stringPer-signer per-payment ceiling → on-chain max_per_payment
expiresAtISO-8601, ms epoch, or omit/nullWall-clock expiry; omit for no expiry (0 on chain)

Also on create:

FieldMeaning
agentAddressAgent public Sui address only
nameHuman label for the logical signer
profileIdsOptional profiles (resolved to policy ids before tx build)
policyIdsOptional off-chain policy row ids
idempotencyKeyRequired; scopes the whole multi-asset aggregate

Aggregate status

StatusMeaning
awaiting_owner_signatureTx kinds prepared; owner must sign
partially_activeSome but not all per-asset grants finalized
activeEvery expected GrantCreated verified
revoking / partially_revoked / revokedTerminal revoke path
expired / failedTerminal non-spend / error
pausing / pausedReversible 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": "..."
}
CodeTypical HTTP
UNAUTHENTICATED401
NOT_FOUND404
CONFLICT / NOT_FINAL409
INVALID / UNSUPPORTED_ASSET400
UNAVAILABLE503
TX_FAILED / FOREIGN_DIGEST / UNVERIFIED502

Guides