Error Codes
All errors returned by PermitNetworks follow a consistent structure. Machine-readable error codes let you handle specific failure cases in your application.
{
"error": {
"code": "auth.invalid_key", // machine-readable code
"message": "API key is invalid", // human-readable description
"request_id": "req_01hwxyz..." // include this when contacting support
}
}
// Note: decision deny is NOT an HTTP error — it returns 200:
{
"id": "dec_01hwxyz...",
"effect": "deny",
"reason": "policy.not_found", // same code format
...
}auth
4 codesauth.invalid_keyHTTP 401API key is invalidThe API key passed in the Authorization header does not exist, has been revoked, or has a typo.
Verify the key in Dashboard → API Keys. Ensure you are using the full key including the pn_live_sk_ or pn_test_sk_ prefix.
auth.expiredHTTP 401Permit token has expiredThe Ed25519 permit token passed to a downstream service has exceeded its TTL (default: 5 minutes).
Request a new authorization via permit.authorize(). Consider reducing the time between authorization and action execution.
auth.signature_invalidHTTP 401Permit token signature failed verificationThe Ed25519 signature on the permit token is not valid. This can happen if the token was tampered with, or if you are verifying with the wrong public key.
Fetch the current public key from /.well-known/jwks.json and re-verify. Never store public keys for long periods — refresh them periodically.
auth.scope_insufficientHTTP 403API key lacks required scopeThe operation requires a scope that the current API key does not have. For example, creating a policy requires policies:write scope.
Create a new API key with the required scope, or add the scope to the existing key in Dashboard → API Keys.
policy
3 codespolicy.not_foundHTTP 200No matching policy (default deny)The authorization request did not match any enabled policy. PermitNetworks defaults to deny when no policy matches.
Create a policy in the Dashboard or via POST /v1/policies that covers this agent + action combination.
policy.conflictHTTP 200Multiple policies matched with conflicting effectsTwo or more policies matched the request with different effects. The lowest-priority-number policy wins. This is returned as a warning in the decision metadata.
Review overlapping policies and clarify intent by adjusting priorities, adding agent-specific filters, or narrowing action/resource patterns.
policy.disabledHTTP 200Matching policy is disabledA policy matched but is in disabled state. It is treated as if it does not exist for authorization purposes.
Enable the policy in the Dashboard or via PUT /v1/policies/:id.
rate limit
1 coderate_limit.exceededHTTP 429Request rate limit exceededThe agent has exceeded the rate limit defined in the matching policy (per_second, per_minute, etc.), or the API key has exceeded the platform-level rate limit.
Implement exponential backoff using the Retry-After response header. For persistent issues, review policy rate_limit settings or contact support to increase platform limits.
budget
2 codesbudget.exceededHTTP 200Agent budget limit reachedThe agent's budget (fiat or API call) has been fully consumed for the current period. All subsequent requests from this agent will be denied until the period resets.
Wait for the budget period to reset, or increase the budget limit in the policy. A budget.exceeded webhook is fired when this happens.
budget.below_thresholdHTTP 200Budget threshold crossed (informational)This is not an error — it appears in webhook events when budget consumption crosses the configured threshold_alert percentage. Decisions still return allow.
No action required. Configure a budget.threshold webhook handler to alert your team before the limit is reached.
resource
1 coderesource.not_allowedHTTP 200Resource is outside the policy scopeThe requested resource did not match the resource patterns defined in any allow rule. The policy explicitly restricts this resource.
Check that the resource string matches the patterns in your policy. Ensure you are not passing a more specific resource than the policy's wildcard covers.
decision
3 codesdecision.already_confirmedHTTP 409Decision was already confirmedYou called confirm() on a decision that is already in confirmed state. This is a no-op but returns 409 to prevent double-counting.
Track decision state in your application. Do not call confirm() more than once per decision ID.
decision.expiredHTTP 410Decision has expiredThe decision's TTL has elapsed. It can no longer be confirmed or rejected. The permit token derived from this decision is also expired.
Request a new authorization via permit.authorize(). Reduce latency between authorization and action execution.
decision.not_foundHTTP 404Decision ID does not existThe decision ID was not found. Either it was never created, belongs to a different account, or was from a different environment.
Verify the decision ID. Check you are using the correct API key (test vs live environment).
validation
2 codesvalidation.missing_fieldHTTP 400Required field missing in request bodyA required field (e.g. agent_id or action in POST /v1/decisions) was not included in the request body.
Check the API reference for required fields and ensure your request body is valid JSON.
validation.invalid_actionHTTP 400Action string is not validThe action string contains invalid characters or does not follow the namespace.verb convention.
Actions must match the pattern [a-z][a-z0-9_]*\.[a-z][a-z0-9_]* (e.g. payment.create, data.read). Wildcards (*) are only valid in policy definitions, not in authorization requests.
Need Help?
If you are seeing an error not listed here, or an error you cannot resolve, contact support with the request_id from the error response. Each request has a unique ID we can use to look up the full trace.