| name | managing-agent |
| description | Day-2 operations for a provisioned Turnkey agent: debug denied transactions, update policies (spending limits, allowlists), rotate API keys, revoke access, and add chains. Requires root credentials. For initial agent setup, use provisioning-agent. |
| license | Apache-2.0 |
| compatibility | Requires Turnkey root credentials (P-256 key pair). All recipes run with root/admin access. |
| metadata | {"author":"turnkey","tags":"workflow agent management key-rotation revocation debugging policy-update"} |
Managing an Agent
Calling the API: JSON bodies below are the parameters object accepted by @turnkey/sdk-server methods (e.g. create_api_keys → client.createApiKeys(...), update_policy → client.updatePolicy(...)). See the root SKILL.md for SDK setup and full endpoint-to-method mapping.
Independent recipes for day-2 agent operations. Each section is self-contained — find the one that matches your situation.
All recipes run with root credentials (or credentials with sufficient policy permissions). These are admin operations, not actions the agent performs on itself.
Base URL: https://api.turnkey.com
Rules (mandatory)
- Human confirmation before any policy change. Display the updated policy and explain what changes. Wait for explicit approval.
- Before deleting an agent user, verify it is the intended non-root agent. Deleting users is permanent. If the target might be a root, admin, or human user — or you cannot confirm the target is the disposable provisioned agent — do not call
delete_users; stop and ask the human to confirm the exact user.
Prerequisites
Requires root API credentials. All recipes run with root/admin access, not agent credentials.
TURNKEY_API_PUBLIC_KEY= # Root API key — public component (hex)
TURNKEY_API_PRIVATE_KEY= # Root API key — private component (P-256 hex)
TURNKEY_ORGANIZATION_ID= # Turnkey organization UUID
Use the getting-started skill if you still need to verify credentials.
My agent's transaction was denied
Use get_policy_evaluations to see exactly which policy blocked it and why.
POST /public/v1/query/get_policy_evaluations
{
"organizationId": "<ORG_ID>",
"activityId": "<DENIED_ACTIVITY_ID>"
}
The response contains a policyEvaluations array. Each entry shows a policy and its outcome:
| Outcome | Meaning |
|---|
OUTCOME_ALLOW | This policy permitted the action |
OUTCOME_DENY_EXPLICIT | This policy explicitly blocked the action (a DENY matched) |
OUTCOME_DENY_IMPLICIT | No ALLOW policy matched — blocked by default deny |
OUTCOME_REQUIRES_CONSENSUS | Policy requires multi-party approval before proceeding |
OUTCOME_ERROR | Policy evaluation errored (likely a no-short-circuit issue — see managing-policies) |
Common causes:
OUTCOME_DENY_EXPLICIT: A DENY policy's condition matched. Check the spending cap or address allowlist. Either lower the transaction amount / change the destination, or update the DENY policy with the human's approval.
OUTCOME_DENY_IMPLICIT: No ALLOW policy matched. The agent's ALLOW policy condition doesn't cover this action. Check that wallet.id, chain-specific conditions, or consensus expressions match.
OUTCOME_ERROR: A policy condition errored during evaluation. Most common cause: mixing wallet.id and private_key.id in one condition (no-short-circuit rule). Split into separate policies.
Do not broaden policies without revisiting the original constraint decisions with the human. A denied transaction may be the policy working correctly.
For full debugging examples, see references/policy-debugging-examples.md.
I need to change spending limits or allowed addresses
Find the policy to update
POST /public/v1/query/list_policies
{
"organizationId": "<ORG_ID>"
}
Identify the relevant policy by name (e.g., deny-large-eth for spending cap, agent-eth-allowlist for address restrictions).
Update the policy
Confirm the change with the human before submitting.
POST /public/v1/submit/update_policy
Example — increase spending cap from 0.1 ETH to 0.5 ETH:
{
"policyId": "<POLICY_ID>",
"policyName": "deny-large-eth",
"policyEffect": "EFFECT_DENY",
"policyCondition": "eth.tx.value > 500000000000000000",
"policyNotes": "Block transfers above 0.5 ETH (was 0.1 ETH)"
}
Example — add a new address to the allowlist:
{
"policyId": "<POLICY_ID>",
"policyName": "agent-eth-allowlist",
"policyEffect": "EFFECT_ALLOW",
"policyConsensus": "approvers.any(user, user.tags.contains('<AGENT_TAG_ID>'))",
"policyCondition": "wallet.id == '<WALLET_ID>' && eth.tx.to in ['0xAddr1', '0xAddr2', '0xNewAddr3']",
"policyNotes": "Added 0xNewAddr3 to allowlist"
}
After updating, list policies again and confirm the full active set with the human.
For more examples, see references/policy-update-examples.md.
I need to rotate the agent's API key
Rotate without downtime. Each step must succeed before proceeding.
Step 1: Generate a new P-256 key pair locally.
Step 2: Register the new public key (sign this with the root or old agent key):
POST /public/v1/submit/create_api_keys
{
"userId": "<AGENT_USER_ID>",
"apiKeys": [{
"apiKeyName": "agent-key-v2",
"publicKey": "<NEW_PUBLIC_KEY>",
"curveType": "API_KEY_CURVE_P256"
}]
}
Step 3: Verify the new key works (sign this with the new key):
POST /public/v1/query/whoami
{
"organizationId": "<ORG_ID>"
}
If this returns the agent's user details, the new key is working.
Step 4: Delete the old key (sign this with the new key):
POST /public/v1/submit/delete_api_keys
{
"userId": "<AGENT_USER_ID>",
"apiKeyIds": ["<OLD_KEY_ID>"]
}
Step 5: Update the agent's runtime environment with the new TURNKEY_API_PUBLIC_KEY and TURNKEY_API_PRIVATE_KEY.
For the complete rotation workflow with full request/response, see references/key-rotation-examples.md.
I need to revoke agent access immediately
For emergency shutdown of a provisioned agent, prefer deleting the agent user. This immediately revokes all of that user's credentials and avoids the delete_api_keys failure case where Turnkey refuses to leave a surviving user with zero valid auth methods.
Safety gate — do not skip: before deletion, confirm the target is the intended disposable, non-root agent user. Check the user record and match it against the operator's intent (agent user ID, name, tags, and known provisioning notes). If the user might be root/admin/human, or if you cannot confirm it is the agent, do not delete it. Stop and ask the human to confirm the exact user first.
POST /public/v1/query/get_user
{
"organizationId": "<ORG_ID>",
"userId": "<AGENT_USER_ID>"
}
After the safety gate passes, present the deletion call, warn that it is permanent and irreversible, and wait for explicit human confirmation:
POST /public/v1/submit/delete_users
{
"userIds": ["<AGENT_USER_ID>"]
}
This takes effect immediately when the request succeeds: the deleted agent user can no longer authenticate or sign. When responding to a compromise or shutdown request, state this explicitly so the operator knows access has stopped.
Use delete_api_keys only when removing one compromised key from a user that will still have another valid credential (for example, after key rotation). Do not use it to delete the user's only credential; Turnkey will reject that with user missing valid credential.
If the safety gate does not pass, do not delete the user. Instead, stop and ask for operator review; if the goal is only to stop signing while identity is investigated, use a narrowly-scoped DENY policy or remove the agent-specific ALLOW policy with explicit human approval.
After revoking access, optionally clean up:
- Delete the agent's policies (if they were user-specific and no longer needed)
- The wallet remains — it may hold funds that need to be transferred first
I need to add a new chain to the agent's wallet
Derive a new account
POST /public/v1/submit/create_wallet_accounts
{
"walletId": "<AGENT_WALLET_ID>",
"accounts": [{
"curve": "CURVE_ED25519",
"pathFormat": "PATH_FORMAT_BIP32",
"path": "m/44'/501'/0'/0'",
"addressFormat": "ADDRESS_FORMAT_SOLANA"
}]
}
For Bitcoin, remember the dual-account requirement (compressed key + address at same path). See the managing-wallets skill.
Update policies for the new chain
Adding a chain account does NOT automatically grant the agent permission to sign on it. If the agent's ALLOW policy only references eth.tx.* conditions, it won't cover Solana, Tron, Tempo, or Bitcoin transactions.
You may need to:
- Create a new ALLOW policy for the new chain (e.g.,
solana.tx.* for Solana, tron.tx.* for Tron, tempo.tx.* for Tempo, bitcoin.tx.* for Bitcoin)
- Create chain-specific DENY guardrails (e.g., Solana transfer cap, Tron amount cap in SUN, program restrictions)
- Verify the agent can sign on the new chain with a test payload
Confirm all policy changes with the human before creating them.
Troubleshooting
Key rotation: new key doesn't work after registration
Verify the public key format is correct (hex-encoded P-256). Check that the curveType is API_KEY_CURVE_P256. Try whoami signed with the new key to isolate the issue.
Policy update has no effect
List all policies to check for conflicting DENY policies that override the updated ALLOW. Remember: DENY always wins.
Agent still has access after key deletion
Verify the specific compromised or retired key was deleted. If the goal is full emergency shutdown for a disposable non-root agent, use the get_user safety gate plus delete_users flow above instead of trying to remove every API key; Turnkey rejects deleting a user's only valid credential.
New chain added but agent can't sign on it
Policies are chain-specific. An ALLOW with eth.tx.to in [...] doesn't cover Solana, Tron, Tempo, or Bitcoin. Create a separate policy with the appropriate namespace (solana.tx.*, tron.tx.*, tempo.tx.*, bitcoin.tx.*).
Related Skills
provisioning-agent — initial agent setup (run this first)
managing-policies — full policy reference, language, anti-patterns
managing-users — user and API key details
managing-wallets — wallet accounts and chain support
signing-transactions — what the agent does with its wallet