Authentication & Authorization
API key management, permissions, rate limits, and security features.
API Keys
All API requests are authenticated with Bearer tokens:
Authorization: Bearer wg_test_your_key_hereKey Prefixes
| Prefix | Environment | Description |
|---|---|---|
wg_test_ | Sandbox | Access sandbox wallets only |
wg_live_ | Production | Access production wallets only |
Keys are hashed with SHA-256 before storage. The raw key is only shown once at creation time.
Key Properties
| Property | Description |
|---|---|
permissions | Array of granted permissions |
walletScope | Restrict access to specific wallet IDs |
allowedIps | IP allowlist (CIDR or exact) |
expiresAt | Optional expiry date |
Permissions
Every API key must have explicit permissions. Keys without permissions are denied access.
| Permission | Description |
|---|---|
wallets:read | List and get wallets |
wallets:write | Create, fund, freeze, close wallets |
payments:read | List payments, transfers, cards |
payments:write | Send payments, create cards, initiate transfers |
policies:read | List policies |
policies:write | Create, update, delete policies |
webhooks:read | List webhooks |
webhooks:write | Create, update, delete webhooks |
invoices:read | List invoices and customers |
invoices:write | Create invoices, charge customers |
cards:sensitive_read | Access full card number and CVC (audit logged) |
Permission Checks
The API returns 403 with PERMISSION_DENIED when a key lacks the required permission:
{
"error": {
"code": "PERMISSION_DENIED",
"message": "Missing required permission: payments:write"
}
}Wallet Scoping
API keys can be scoped to specific wallets using the walletScope property. A scoped key can only access the listed wallets — requests to other wallets return 403.
const key = await wg.apiKeys.create({
name: 'Agent-specific key',
permissions: ['wallets:read', 'payments:write'],
walletScope: ['wal_01J_agent_1', 'wal_01J_agent_2'],
});IP Allowlists
Restrict API key usage to specific IP addresses or CIDR ranges.
const key = await wg.apiKeys.create({
name: 'Office-only key',
permissions: ['wallets:read'],
allowedIps: ['203.0.113.0/24', '198.51.100.42'],
});Requests from non-allowed IPs return:
{
"error": {
"code": "IP_NOT_ALLOWED",
"message": "Request IP not in allowlist"
}
}Environment Isolation
Sandbox and production environments are fully isolated:
- Sandbox keys (
wg_test_*) can only access sandbox wallets - Production keys (
wg_live_*) can only access production wallets - Cross-environment access returns
403 ENVIRONMENT_MISMATCH - Production keys require completed activation (KYB + ToS + billing)
Rate Limits
Rate limits are enforced per API key per minute, based on your plan:
| Plan | Read RPM | Write RPM | Wallets |
|---|---|---|---|
| Free | 60 | 10 | 3 |
| Starter | 200 | 50 | 10 |
| Growth | 500 | 100 | 50 |
| Enterprise | 2,000 | 500 | Unlimited |
When rate limited, the API returns 429 with a Retry-After header:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 45 seconds."
}
}Read operations: GET requests. Write operations: POST, PUT, PATCH, DELETE requests.
Idempotency Keys
For payment and funding operations, include an idempotencyKey to prevent duplicate transactions. Keys are valid for 24 hours.
const payment = await wg.payments.send({
fromWalletId: 'wal_source',
toWalletId: 'wal_dest',
amount: '100.00',
idempotencyKey: 'pay_order_12345',
});Duplicate requests with the same key return the cached response. If the same key is reused with different parameters, the API returns 409 IDEMPOTENCY_KEY_REUSE.
Key Lifecycle
| Event | Description |
|---|---|
| Created | Key shown once, hash stored |
| Active | Accepting requests |
| Expiring soon | api_key.expiring_soon webhook sent 7 days before expiry |
| Expired | Requests rejected with API_KEY_REVOKED |
| Auto-revoked | api_key.auto_revoked webhook sent |
| Manually revoked | Immediate rejection |
Error Reference
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key |
API_KEY_REVOKED | 401 | Key has been revoked or expired |
PERMISSION_DENIED | 403 | Missing required permission |
IP_NOT_ALLOWED | 403 | Request IP not in allowlist |
ENVIRONMENT_MISMATCH | 403 | Key environment doesn't match resource |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
ACTIVATION_REQUIRED | 403 | Production activation not completed |