Arca/Documentation
Portal

Scoped Tokens & Permissions

Arca uses an IAM-style, resource/capabilities permission model inspired by AWS. Both scoped JWT tokens (for end-users) and scoped API keys (for services) carry policy statements that grant specific actions on specific Arca path patterns.

Policy Model

A scope contains one or more policy statements. Each statement has an effect ("Allow" or "Deny"), a set of actions, and a set of resources (Arca object path patterns):

json
{
"statements": [
{
"effect": "Allow",
"actions": ["arca:TransferFrom", "arca:ReceiveTo"],
"resources": ["/users/u123/*"]
},
{
"effect": "Allow",
"actions": ["arca:Read"],
"resources": ["*"]
},
{
"effect": "Deny",
"actions": ["arca:*"],
"resources": ["/_internal/*"]
}
]
}

Anything not explicitly allowed is denied by default. Deny statements act as safety rails — they override any Allow statements. Use them to protect sensitive paths even when a broad Allow is in effect.

Action Catalog

Retrieve the full catalog programmatically via GET /api/v1/permissions (no auth required).

Object Lifecycle

ActionDescriptionChecked against
arca:CreateObjectCreate a new Arca objectPath being created
arca:DeleteObjectDelete an Arca objectObject being deleted

Balance (directional)

ActionDescriptionChecked against
arca:TransferFromDebit funds (source side)Source path
arca:ReceiveToReceive funds (deposit, transfer credit, or sweep)Target path
arca:WithdrawFromInitiate outbound withdrawalSource path

Read / Observe

ActionDescription
arca:ReadObjectView object metadata and status
arca:ReadBalanceView object balances
arca:ReadOperationView operations
arca:ReadEventView events
arca:ReadDeltaView state deltas
arca:SubscribeSSE real-time stream

Convenience Aliases

Aliases expand to individual actions. Named aliases are expanded at mint time (stored in the JWT). The wildcard arca:* is stored as-is and matches any action at check time.

AliasExpands to
arca:ReadAll arca:Read* + arca:Subscribe
arca:Transferarca:TransferFrom + arca:ReceiveTo
arca:Fundarca:ReceiveTo + arca:WithdrawFrom
arca:Lifecyclearca:CreateObject + arca:DeleteObject
arca:WriteAll write actions
arca:*Everything (evaluated at check time)

Resource Patterns

  • /treasury/usd — exact match
  • /users/* — matches the prefix and all paths below it
  • * — matches all resources

Path safety note: Arca paths have no directory-traversal semantics. The segments . and .. are treated as literal characters. There is no path normalization — what you see is what matches. This is a deliberate security invariant.

Authorization Principles

  1. Deny by default. Anything not explicitly granted by an Allow statement is denied.
  2. Explicit Deny overrides Allow. If any Deny statement matches an (action, resource) pair, the request is blocked — regardless of any Allow statements. Use Deny as a safety rail to protect sensitive paths (e.g., /_internal/*).
  3. Actions are directional and per-resource. For operations touching multiple objects, each side is checked independently. A transfer checks arca:TransferFrom on the source and arca:ReceiveTo on the target.
  4. Inbound permissions are unified. arca:ReceiveTo covers deposits, transfer credits, and sweeps. The balance outcome is identical regardless of channel. Outbound actions (arca:TransferFrom, arca:WithdrawFrom) remain separate because they cross different trust boundaries.
  5. Delete + sweep is compound authorization. Deleting an object with balance requires arca:DeleteObject on the source and arca:ReceiveTo on the sweep target. Without a sweep target, arca:DeleteObject alone suffices for zero-balance objects.
  6. Realm is a boundary, not a resource. Scoped tokens are locked to one realm. Resource patterns do not encode realm — this simplifies matching.

Mint Scoped Token

POST/api/v1/auth/tokenJWT / API Key

Mint a scoped JWT bound to a single realm with IAM-style policy statements. The resulting token is passed from the builder's backend to the end-user's frontend.

Request Body

realmIdstringrequired
The realm this token is locked to.
substringrequired
Opaque end-user identifier (builder-defined, e.g., user ID).
scopeobjectrequired
Policy scope with statements array. Each statement has effect ("Allow" or "Deny", default "Allow"), actions (action strings or aliases), and resources (path patterns).
expirationMinutesnumber
Token TTL in minutes (1–1440). Default: 60.

Response 201 Created

json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresAt": "2026-02-14T15:00:00Z"
}
}

Example

bash
# Builder's backend mints a token for user "alice"
curl -X POST http://localhost:8080/api/v1/auth/token \
-H "Authorization: Bearer arca_78ae7276_..." \
-H "Content-Type: application/json" \
-d '{
"realmId": "6d25623e-...",
"sub": "alice",
"scope": {
"statements": [
{
"effect": "Allow",
"actions": ["arca:Read"],
"resources": ["*"]
},
{
"effect": "Allow",
"actions": ["arca:TransferFrom", "arca:ReceiveTo"],
"resources": ["/users/alice/*"]
},
{
"effect": "Deny",
"actions": ["arca:*"],
"resources": ["/_internal/*"]
}
]
},
"expirationMinutes": 30
}'

Permissions Catalog

GET/api/v1/permissionsNone

Returns the full action catalog with descriptions, grouped by category, plus all available aliases. No authentication required.

Auth Audit Log

GET/api/v1/auth/auditJWT / API Key

Returns a paginated list of authentication and authorization events for the authenticated builder. Tracks sign-ins, token minting, API key usage, and permission denials.

Query Parameters

eventTypestring
Filter by event type: sign_in, token_minted, api_key_authenticated, permission_denied.
realmIdstring
Filter by realm ID.
sincestring
ISO-8601 timestamp — only return events after this time.
untilstring
ISO-8601 timestamp — only return events before this time.
successboolean
Filter by outcome: true for successful events, false for denials.
limitnumber
Max entries per page (1–200, default 50).
offsetnumber
Pagination offset (default 0).

Response 200 OK

json
{
"success": true,
"data": {
"entries": [
{
"id": "...",
"builderId": "...",
"eventType": "token_minted",
"actorType": "builder",
"actorId": "...",
"realmId": "...",
"subject": "alice",
"scopeSummary": "{...}",
"tokenJti": "...",
"expiresAt": "2026-02-21T15:00:00Z",
"success": true,
"operationCount": 12,
"createdAt": "2026-02-21T14:00:00Z"
}
],
"total": 42
}
}

Scoped tokens can access this endpoint with the arca:ReadAuditLog action (included in the arca:Read alias). The operationCount field on token_minted entries shows how many operations were triggered by that token's subject.

Credential Scope Lookup

GET/api/v1/auth/audit/scopeJWT / API Key

Returns the permissions (scope) of a specific credential — either a scoped JWT (by tokenJti) or an API key (by apiKeyId). Use this to inspect what permissions a credential carried when it was used to perform operations.

Query Parameters

tokenJtistring
The JWT ID of a scoped token. Looks up the token_minted audit entry to retrieve the scope.
apiKeyIdstring
The API key ID. Retrieves scope from the api_keys table.

Response 200 OK

json
{
"success": true,
"data": {
"credentialType": "scoped_token",
"credentialId": "abc-123-jti",
"subject": "alice",
"scope": "{\"statements\":[...]}",
"fullAccess": false,
"createdAt": "2026-02-21T14:00:00Z",
"expiresAt": "2026-02-21T15:00:00Z"
}
}

Requires the arca:ReadAuditLog permission. For API keys without a scope, fullAccess is true and scope is null.

Scope Enforcement

When a scoped token (or scoped API key) is used, the API enforces:

  1. Realm lock: Scoped tokens can only access the realm specified at mint time. Requests to other realms return 403 FORBIDDEN.
  2. Policy evaluation: Each operation produces one or more (action, resource) pairs. For each pair: (a) if any Deny statement matches, access is blocked; (b) if any Allow statement matches, access is granted; (c) otherwise, access is denied (implicit deny). All pairs must pass for the request to succeed.
  3. Resource matching: Requested paths must match at least one pattern in the granting statement. Patterns ending in /* match all descendants.

Integration Flow

text
BuilderBackend ArcaAPI EndUserFrontend
| | |
|--- POST /auth/token --->| |
|<-- scoped JWT ----------| |
| | |
|--- hand JWT to user --->| |
| | |
| |<-- POST /transfer -|
| | (Bearer scoped) |
| |-- verify + enforce -|
| | |
| |--- response ------>|

Security Pre-flight Checklist

When exposing Arca to end-users via scoped tokens, the following items remain in the builder's domain. Arca enforces token scope at the API layer, but the builder is responsible for minting correct tokens in the first place.

Review before going live

Each item below represents a security boundary that Arca cannot yet make completely foolproof. Address all of them before shipping scoped-token access to production.

  1. Never expose API keys on the frontend. API keys are team-scoped with full access to every realm. Always use scoped tokens for end-user-facing code. If an API key leaks, revoke it immediately from the API Keys page.
  2. Scope tokens to the user's Arca paths. When minting a token, restrict paths to the current user's subtree (e.g., /users/{userId}/*). A token with paths: ["/*"] gives access to every object in the realm.
  3. Grant minimal permissions. Only include the permissions the user actually needs. Most end users need read and transfer — not delete or create.
  4. Set short expiry times. Frontend tokens should expire in 15–60 minutes. The builder's backend should handle refresh by minting a new token when the current one is close to expiry.
  5. Validate scope server-side before minting. The builder's backend must verify that the requesting user is entitled to the paths and permissions in the token. Arca trusts the builder to get this right — it has no knowledge of the builder's user model.
  6. Do not let the frontend request its own scope. The end-user's frontend should not send the desired scope to the builder's backend. The backend should derive the scope from the user's authenticated identity and business rules.
  7. Use distinct realms for dev and production. Demo realm data is simulated and has permissive defaults. Never use a demo realm for real transactions.
  8. Treat operation paths as idempotency keys. If a transfer path is reused, the original result is returned (no double-spend). Build unique paths — include a nonce or UUID (e.g., /op/transfer/{userId}-{uuid}).
  9. Monitor via events. Subscribe to realm events (SSE or polling) to detect unexpected activity — transfers you did not initiate, objects created outside your expected paths, etc.

Error Handling

Errors follow a consistent structure with a machine-readable code and a human-readable message.

Error Codes

CodeHTTP StatusDescription
VALIDATION_ERROR400Input validation failed
UNAUTHORIZED401Missing or invalid token
FORBIDDEN403Token scope does not permit this action
SIGNUP_FAILED400 / 409Sign-up failed (invalid input or duplicate email)
SIGNIN_FAILED401Invalid email or password
NOT_FOUND404Resource not found or not accessible
DUPLICATE_REALM409A realm with this slug already exists
ALREADY_REVOKED409API key is already revoked
CONFLICT409Duplicate resource or invalid state transition (explorer)
INTERNAL_ERROR500Unexpected server error

Example Error Response

json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Realm name must be 100 characters or fewer"
}
}