Admin API
Manage users, sessions, and delegations from your backend using the KevoAdmin class from @kevo-ws/sdk/server — or via raw HTTP with your project secret key.
sk_…) in client-side code, browser bundles, or mobile apps.Setup
npm install @kevo-ws/sdk
import { KevoAdmin } from '@kevo-ws/sdk/server'
const admin = new KevoAdmin({
secretKey: process.env.KEVO_SECRET_KEY!, // sk_...
// apiUrl: 'https://api.kevo.ws' // optional, defaults to production
evmRpcUrl: 'https://mainnet.base.org', // optional — used by sendTransaction
solanaRpcUrl: 'https://api.mainnet-beta.solana.com', // optional — used by sendSolanaTransaction
})KevoAdmin is a plain Node.js class — no browser globals, no DOM, no React. It works in any server environment: Express, Fastify, Next.js API routes, Edge Functions, background workers, cron jobs.
Authentication
Your secret key is found in the Kevo Portal under Settings → Secret Key. The SDK automatically sends it as Authorization: Bearer sk_… on every request.
If a request fails, KevoAdmin throws a KevoAdminError with.status (HTTP code) and .message (human-readable reason).
import { KevoAdmin, KevoAdminError } from '@kevo-ws/sdk/server'
try {
const { user } = await admin.users.get('user-uuid')
} catch (err) {
if (err instanceof KevoAdminError) {
console.error(err.status, err.message) // e.g. 404, "User not found"
}
}| Status | Meaning |
|---|---|
401 | Missing, malformed, or invalid secret key |
403 | Delegation policy violation (sign methods) |
404 | User or delegation not found in your project |
422 | Delegation exists but is missing the required wallet capability |
502 / 504 | Internal signing service unavailable or timed out |
Users
List users
/v1/admin/usersSecret keyPaginated list of all users in your project.
const { users, limit, offset } = await admin.users.list({
limit: 50, // max 100
offset: 0,
})
// users: Array<{
// id: string
// did: string
// createdAt: string
// authMethods: { type: string; identifier: string }[]
// evmAddress: string | null
// }>Get user detail
/v1/admin/users/:userIdSecret keyFull detail: auth methods, EVM wallet, Solana wallet, delegation status.
const { user } = await admin.users.get('a1b2c3d4-...')
// user: {
// id, did, createdAt,
// authMethods: [{ type: 'email', identifier: '[email protected]' }],
// evmWallet: { address: '0x...', createdAt: '...' } | null
// solanaWallet: { address: '8vCy...', createdAt: '...' } | null
// delegation: AdminDelegation | null
// }Revoke all sessions
/v1/admin/users/:userId/revoke-sessionsSecret keyForce sign-out a user by invalidating all active sessions.
await admin.users.revokeSessions('a1b2c3d4-...')
// throws KevoAdminError(404) if user not foundDelegations
A delegation lets your backend sign transactions on behalf of a user without them being online. The user grants it from the client (see Delegations doc), then your backend calls the sign methods below.
List active delegations
/v1/admin/delegationsSecret keyAll active delegations in your project.
const { delegations } = await admin.delegations.list()
// delegations: Array<{
// id, userId, walletId, solanaWalletId,
// policies: DelegationPolicies | null,
// txCount: number,
// active: boolean,
// createdAt: string,
// revokedAt: string | null
// }>Sign EVM hash (delegated)
/v1/admin/delegations/:userId/sign-evmSecret keySign a 32-byte keccak256 hash on behalf of the user.
const result = await admin.delegations.signEvm('user-uuid', {
hash: 'a3f1e0d2b9c4...64hexchars', // 64 hex chars, no 0x prefix
chainId: 8453, // optional — enforced against policy
to: '0xUniswapRouter...', // optional — enforced against allowedContracts
value: '0', // optional — wei decimal, enforced against maxAmountWei
})
// result: { r: '0x...', s: '0x...', v: 28, signature: '0x...' }
// signature is 65 bytes: r(32) || s(32) || v(1)Provide chainId, to, and value whenever your delegation has matching policies — the API enforces them before reaching the signing service, so omitting them would bypass policy checks.
Sign Solana transaction (delegated)
/v1/admin/delegations/:userId/sign-solanaSecret keySign a Solana transaction message on behalf of the user.
import { Transaction } from '@solana/web3.js'
// Build your transaction, then serialize the message:
const tx = new Transaction().add(/* your instruction */)
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
tx.feePayer = new PublicKey(userSolanaAddress)
const messageBase64 = Buffer.from(tx.serializeMessage()).toString('base64')
const { signature } = await admin.delegations.signSolana('user-uuid', {
message: messageBase64,
})
// Re-attach the signature and broadcast:
tx.addSignature(
new PublicKey(userSolanaAddress),
Buffer.from(signature, 'base64'),
)
await connection.sendRawTransaction(tx.serialize())Send EVM Transaction (sign + broadcast)
sendTransaction() is a high-level helper that signs and broadcasts an EVM transaction in one call. It automatically fills nonce, gasLimit, and maxFeePerGas from the RPC if not provided. Returns the transaction hash.
/v1/admin/delegations/:userId/sign-evm-txSecret keySign a full EVM transaction (called internally by sendTransaction).
// Simplest usage — just to, value, chainId:
const txHash = await admin.delegations.sendTransaction(
'user-uuid',
{
to: '0xRecipient...',
value: '0x2386f26fc10000', // 0.01 ETH in hex
chainId: 8453, // Base
},
)
console.log('TX hash:', txHash)
// With calldata (e.g. ERC-20 transfer):
const txHash2 = await admin.delegations.sendTransaction(
'user-uuid',
{
to: '0xUSDC...',
data: '0xa9059cbb' + '...', // transfer(address,uint256)
value: '0x0',
chainId: 8453,
},
)
// Override gas/nonce if needed:
const txHash3 = await admin.delegations.sendTransaction(
'user-uuid',
{
to: '0x...',
data: '0x...',
chainId: 8453,
nonce: 42,
gasLimit: '0x30000',
maxFeePerGas: '0x59682F00',
maxPriorityFeePerGas: '0x59682F00',
},
'https://custom-rpc.example.com', // optional per-call RPC override
)rpcUrl argument → chains[tx.chainId] from KevoAdminConfig → evmRpcUrl from KevoAdminConfig. If none is set, the call throws immediately.Send Solana Transaction (sign + broadcast)
sendSolanaTransaction() signs and broadcasts a Solana transaction in one call. Pass the unsigned message bytes (or base64); the SDK handles signature assembly and RPC broadcast. Returns the transaction signature (base58).
import { Transaction, PublicKey, SystemProgram } from '@solana/web3.js'
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: new PublicKey(userSolAddress),
toPubkey: new PublicKey('Recipient...'),
lamports: 1_000_000, // 0.001 SOL
}),
)
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash
tx.feePayer = new PublicKey(userSolAddress)
const sig = await admin.delegations.sendSolanaTransaction(
'user-uuid',
{ message: tx.serializeMessage() }, // Uint8Array or base64 string
// optional per-call RPC override:
// 'https://api.mainnet-beta.solana.com'
)
console.log('Solana tx sig:', sig)Full example
A Next.js API route that sends a USDC transfer on behalf of a user with an active delegation. Using sendTransaction, no manual hash signing or gas estimation required:
// app/api/transfer/route.ts
import { KevoAdmin, KevoAdminError } from '@kevo-ws/sdk/server'
import { encodeFunctionCall } from '@kevo-ws/sdk/helpers'
const admin = new KevoAdmin({
secretKey: process.env.KEVO_SECRET_KEY!,
evmRpcUrl: 'https://mainnet.base.org',
})
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' // USDC on Base
export async function POST(req: Request) {
const { userId, recipient, amount } = await req.json()
// Build TransactionRequest using the SDK helper
const tx = encodeFunctionCall({
to: USDC,
functionSignature: 'transfer(address,uint256)',
params: [
{ type: 'address', value: recipient },
{ type: 'uint256', value: BigInt(amount) },
],
chainId: 8453,
})
try {
const txHash = await admin.delegations.sendTransaction(userId, {
to: tx.to,
data: tx.data,
value: '0x0',
chainId: tx.chainId,
})
return Response.json({ txHash })
} catch (err) {
if (err instanceof KevoAdminError) {
return Response.json({ error: err.message }, { status: err.status })
}
throw err
}
}Raw HTTP reference
If you're not using Node.js or prefer bare HTTP, all endpoints accept Authorization: Bearer sk_… and return JSON.
# List users
GET /v1/admin/users?limit=50&offset=0
Authorization: Bearer sk_...
# Get user
GET /v1/admin/users/:userId
Authorization: Bearer sk_...
# Revoke sessions
POST /v1/admin/users/:userId/revoke-sessions
Authorization: Bearer sk_...
# List delegations
GET /v1/admin/delegations
Authorization: Bearer sk_...
# Sign EVM hash
POST /v1/admin/delegations/:userId/sign-evm
Authorization: Bearer sk_...
Content-Type: application/json
{ "hash": "64hexchars", "chainId": 8453, "to": "0x...", "value": "0" }
# Sign Solana message
POST /v1/admin/delegations/:userId/sign-solana
Authorization: Bearer sk_...
Content-Type: application/json
{ "message": "base64EncodedTxMessage..." }
# Sign full EVM transaction (returns rawTransaction for broadcast)
POST /v1/admin/delegations/:userId/sign-evm-tx
Authorization: Bearer sk_...
Content-Type: application/json
{
"chainId": 8453,
"to": "0x...",
"data": "0xa9059cbb...",
"value": "0x0",
"nonce": 42,
"gasLimit": "0x30000",
"maxFeePerGas": "0x59682F00",
"maxPriorityFeePerGas": "0x59682F00"
}