Cost, audit, ClawHub & credentials API
Cost tracking and budget status, audit log query/verify, the ClawHub marketplace, and the encrypted credential and gcloud config stores.
These endpoints cover the operational and security surface of a running Revka gateway: how much your agents are spending, the tamper-evident audit trail of security-relevant events, the ClawHub skill marketplace, and the two metadata-only stores that back workflow credentials and Cloud Run configuration. Use them to drive the dashboard’s cost and audit pages, scrape spend into your own tooling, install community skills, or wire workflow steps to encrypted credentials.
Most routes here require a bearer token from the pairing flow. A few are deliberately different: GET /api/cost is unauthenticated read-only telemetry, while the cost-ingest and credential-resolve endpoints require the internal service token rather than a user bearer token — these are called out explicitly. For the configuration side of cost and observability, see Cost tracking & budgets and Observability & tracing; for the audit log internals, see Audit log.
All /api/* routes share a 64 KiB request body cap and a configurable timeout (default 30 s, env REVKA_GATEWAY_TIMEOUT_SECS), with per-route overrides where noted.
Cost tracking
Section titled “Cost tracking”Revka persists every LLM API call’s token counts and computed USD cost to a JSONL ledger at <workspace>/state/costs.jsonl. The tracker rolls up session, daily, and monthly totals and breaks spend down by model, by agent, and by runtime source (gateway, channel, sidecar). A process-global singleton ensures the gateway and channel paths share a single ledger and a single budget check. A legacy .revka/costs.db store is migrated automatically on first start, and malformed JSONL lines are skipped with a warning rather than failing the read.
Cost is computed as (tokens / 1_000_000) × price_per_million, using the per-model prices configured under [cost.prices]. Model lookup is fuzzy: Revka tries an exact match, then provider/model, then the suffix after /, then a prefix match. When no price entry matches, the record is stored at zero cost (with a debug log).
Gateway cost endpoint
Section titled “Gateway cost endpoint”GET /api/cost returns the cost summary. It requires no auth — it is read-only operator telemetry consumed by the dashboard. When cost tracking is disabled, it returns a fully zeroed summary (with budget.state set to "disabled") rather than an error, so dashboards degrade gracefully.
GET /api/cost{ "cost": { "session_cost_usd": 0.42, "daily_cost_usd": 1.87, "monthly_cost_usd": 12.34, "total_tokens": 184320, "request_count": 57, "by_model": { }, "by_agent": { }, "by_source": { }, "budget": { "enabled": true, "daily_limit_usd": 10.0, "monthly_limit_usd": 100.0, "warn_at_percent": 80, "daily_remaining_usd": 8.13, "monthly_remaining_usd": 87.66, "daily_percent": 18.7, "monthly_percent": 12.34, "state": "ok" } }}The by_model, by_agent, and by_source breakdowns cover the current process lifetime only; daily_cost_usd and monthly_cost_usd are read from the full ledger. Records with no source tag are bucketed under "runtime" in by_source.
To feed token usage from a trusted sidecar (for example the operator-mcp runtime) into the shared ledger, POST /api/cost/usage. Because this endpoint mutates the budget ledger, it is gated by the service token — present it in the X-Revka-Service-Token header, not as a bearer token.
POST /api/cost/usageX-Revka-Service-Token: <service-token>Content-Type: application/json{ "model": "gpt-4o", "provider": "openai", "input_tokens": 1000, "output_tokens": 250, "source": "sidecar", "agent_id": "my-agent", "agent_title": "My Agent"}Only model is required. provider and source default to "sidecar" when omitted or blank. A successful ingest returns { "recorded": true, "usage": { ... } }; if cost tracking is disabled the call returns 200 with { "recorded": false, "reason": "cost tracking disabled" }, and a request without a valid service token returns 401.
Budget enforcement
Section titled “Budget enforcement”Budgets are configured in the [cost] section and enforced per process (not per session) by the global tracker. The warn and exceeded thresholds are compared against the projected total (current usage plus the estimated cost of the pending call), not just the current usage.
[cost]enabled = truedaily_limit_usd = 10.0 # defaultmonthly_limit_usd = 100.0 # defaultwarn_at_percent = 80 # warn at 80% of a limitallow_override = false # allow a --override flag to bypass
[cost.enforcement]mode = "warn" # "warn" | "block" | "route_down"route_down_model = "gpt-4o-mini" # used with "route_down"reserve_percent = 10 # reserve 10% for critical ops
[cost.prices]"claude-sonnet-4-20250514" = { input = 3.0, output = 15.0 } # USD per 1M tokens"gpt-4o" = { input = 2.5, output = 10.0 }The enforcement mode controls what happens when a limit is crossed:
| Mode | Behavior when a budget is exceeded |
|---|---|
warn | Log a warning and allow the request (default) |
block | Reject the request |
route_down | Switch to the cheaper route_down_model |
The budget.state field in the cost summary reflects the current standing: "ok", "warning" (past warn_at_percent), "exceeded" (past a limit), or "disabled". See Cost tracking & budgets for the full configuration reference and the pricing-table format.
Audit log
Section titled “Audit log”The audit log is an append-only, tamper-evident record of security-relevant events: command executions, file access, config changes, auth successes and failures, policy violations, and generic security events. Each entry is linked into a SHA-256 hash chain (entry_hash = SHA-256(prev_hash || canonical_JSON_of_content)) with a monotonically increasing sequence, so modifying any past entry invalidates every entry after it. Entries can optionally be signed with HMAC-SHA256. The log requires [security.audit] enabled = true in config (the default).
[security.audit]enabled = true # defaultlog_path = "audit.log" # relative to the revka dirmax_size_mb = 100 # rotate at this sizesign_events = false # optional HMAC-SHA256 signingGET /api/audit returns recent events, newest first, behind a bearer token.
GET /api/audit?limit=50&event_type=command_execution&since=2026-01-01T00:00:00ZAuthorization: Bearer <token>| Parameter | Type | Default | Meaning |
|---|---|---|---|
limit | int | 50 (max 500) | Maximum events to return |
event_type | string | — | Filter by type (see below) |
since | RFC 3339 | — | Lower bound on the event timestamp |
event_type accepts command_execution, file_access, config_change, auth_success, auth_failure, policy_violation, or security_event. The response wraps the events with a count and an audit_enabled flag:
{ "events": [ { "timestamp": "2026-06-18T10:00:00Z", "event_id": "<uuid>", "event_type": "command_execution", "actor": { "channel": "telegram", "user_id": "123", "username": "@alice" }, "action": { "command": "ls -la", "risk_level": "low", "approved": false, "allowed": true }, "result": { "success": true, "exit_code": 0, "duration_ms": 15 }, "security": { "policy_violation": false, "rate_limit_remaining": 19, "sandbox_backend": "firejail" }, "sequence": 42, "prev_hash": "<64 hex>", "entry_hash": "<64 hex>", "signature": "<64 hex>" } ], "count": 1, "audit_enabled": true}The signature field is present only when sign_events = true. When audit logging is disabled, the endpoint returns 200 with an empty events array, count: 0, and audit_enabled: false — so a dashboard never errors out just because auditing is off.
Audit /verify
Section titled “Audit /verify”GET /api/audit/verify walks the on-disk log and validates the hash chain end to end. It is the fastest way to detect tampering: if any entry was edited, deleted, or reordered, verification fails.
GET /api/audit/verifyAuthorization: Bearer <token>{ "verified": true, "entry_count": 1842 }On a broken chain (or when auditing is not enabled), it returns verified: false with an error describing the failure:
{ "verified": false, "error": "chain broken at sequence 41" }To enable HMAC signing, set a 32-byte key (64 hex characters) in the REVKA_AUDIT_SIGNING_KEY environment variable before the gateway starts — the logger reads it at construction time, and startup fails if the key is missing or the wrong length. Log rotation resets the chain to genesis, so each rotated .N.log archive is independently verifiable. For the chain format, signing setup, and CLI verification, see Audit log.
ClawHub marketplace
Section titled “ClawHub marketplace”ClawHub is the skill marketplace at clawhub.ai. These routes proxy the marketplace API so you can search, browse trending skills, view detail, and one-click-install a skill into your local Kumiho memory and workspace. All four routes require a bearer token.
GET /api/clawhub/search?q=rust+debugging&limit=20GET /api/clawhub/trending?limit=20GET /api/clawhub/skills/{slug}POST /api/clawhub/install/{slug}Authorization: Bearer <token>| Parameter | Where | Default | Meaning |
|---|---|---|---|
q | query | required (search) | Search terms |
limit | query | 20 | Maximum results |
slug | path | — | ClawHub skill slug |
GET /api/clawhub/skills/{slug} returns the skill detail and, when available, attaches the rendered SKILL.md under a skill_md field. POST /api/clawhub/install/{slug} fetches the skill’s SKILL.md, writes it to ~/.revka/workspace/skills/<slug>.md, registers the skill as an item in your Kumiho memory_project’s Skills space (tagging the revision published), and returns:
{ "installed": true, "slug": "rust-debugging", "name": "Rust Debugging", "kref": "kref://CognitiveMemory/Skills/rust-debugging.skill", "description": "Systematic Rust debugging workflow"}If ClawHub is disabled, the routes return 400 with {"error": "ClawHub integration disabled"}; an unreachable marketplace returns 502.
[clawhub] config
Section titled “[clawhub] config”[clawhub]enabled = true # defaultapi_url = "https://clawhub.ai" # default — base URL for the ClawHub APIapi_token = "clh_..." # optional; only required to publish skills| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | bool | true | Enable the ClawHub integration |
api_url | string | "https://clawhub.ai" | Base URL of the ClawHub API (point at a private instance if needed) |
api_token | string? | unset | clh_… token; needed only for publishing — anonymous browsing and installing work without it |
Installed skills use the same registration path as skills created in the dashboard, so they appear in the Skills, tools & integrations pages and the skills API.
Auth profiles — credential store
Section titled “Auth profiles — credential store”Workflow steps that call authenticated services bind to an auth profile: an encrypted credential stored at rest with ChaCha20-Poly1305 AEAD via the gateway’s SecretStore. The store is split into two surfaces with different trust levels — a bearer-auth metadata view, and a service-token-only resolve path that is the only way to decrypt a credential.
GET /api/auth/profiles # list metadata (bearer)POST /api/auth/profiles # create a static-token profile (bearer)DELETE /api/auth/profiles/{id} # delete a profile (bearer)POST /api/auth/profiles/{id}/resolve # decrypt the credential (service token only)GET /api/auth/profiles returns metadata summaries — note the absence of any token field:
{ "profiles": [ { "id": "github:My Token", "provider": "github", "profile_name": "My Token", "kind": "token", "account_id": null, "workspace_id": null, "expires_at": null, "created_at": "2026-06-18T10:00:00Z", "updated_at": "2026-06-18T10:00:00Z" } ]}POST /api/auth/profiles creates a static-token (API-key) profile. The plaintext token is encrypted by the store before persistence and never echoed back; the response is the new metadata summary with 201 Created.
POST /api/auth/profilesAuthorization: Bearer <token>Content-Type: application/json{ "provider": "github", "profile_name": "My Token", "token": "ghp_..." }| Field | Required | Notes |
|---|---|---|
provider | yes | Provider identifier, e.g. github |
profile_name | yes | Human label; combined with provider to form the id |
token | yes | Raw bearer / API key (encrypted at rest) |
account_id | no | Optional account hint |
kind | no | Defaults to "token"; "api_key" is a synonym. "oauth" is rejected with 400 — OAuth profiles must be created through the /config consent flow |
Creation responses worth handling: 400 for missing fields or an unsupported kind, 409 if a profile with the same provider+profile_name already exists, and 429 when the per-IP rate limit trips (10 attempts per 60 s window, then a 5-minute lockout; loopback callers are exempt). The limiter trusts only the socket peer, never X-Forwarded-For.
POST /api/auth/profiles/{id}/resolve decrypts and returns the credential. It requires the X-Revka-Service-Token header (compared in constant time) and sets Cache-Control: no-store on the response so the secret is never cached by intermediaries.
POST /api/auth/profiles/{id}/resolveX-Revka-Service-Token: <service-token>{ "token": "ghp_...", "kind": "token", "provider": "github", "profile_name": "My Token", "expires_at": null}An unknown id returns 404; an empty static token returns 410 Gone (with code auth_profile_empty), and an OAuth profile whose expires_at is in the past returns 410 Gone (with code auth_profile_expired), so the runtime fails the step rather than sending a stale credential.
The service token is generated at gateway startup and persisted to <state_dir>/service-token (mode 0600 on POSIX). It is distinct from the user-facing pairing bearer token — see Pairing & authentication and the Security model.
GCloud configuration profiles
Section titled “GCloud configuration profiles”These metadata-only endpoints list and create named gcloud SDK configuration profiles on the host, backing the workflow editor’s Cloud Run / A2A IAM selector. A gcloud configuration references credentials in the local Cloud SDK credential store but is not itself a secret, so responses carry only display metadata — never access, refresh, or identity tokens. Both routes require a bearer token and shell out to gcloud with a 20 s timeout.
GET /api/gcloud/configsPOST /api/gcloud/configsAuthorization: Bearer <token>GET /api/gcloud/configs runs gcloud config configurations list and returns the parsed configs. If gcloud is not on PATH, it returns available: false (not an error) so the UI can hide the feature:
{ "available": true, "configs": [ { "name": "default", "is_active": true, "project": "construct-498201", "run_region": "us-central1", "compute_region": "us-central1" } ], "error": null}POST /api/gcloud/configs creates a new configuration (via gcloud config configurations create --no-activate) and then sets the supplied properties.
POST /api/gcloud/configsAuthorization: Bearer <token>Content-Type: application/json{ "name": "my-config", "project": "my-gcp-project", "run_region": "us-central1", "compute_region": "us-central1"}| Field | Required | Notes |
|---|---|---|
name | yes | ASCII alphanumerics plus -, _, .; must start with a letter or digit; max 64 chars |
project | yes | GCP project ID (core/project) |
account | no | Account email (core/account) |
run_region | no | Cloud Run region (run/region) |
compute_region | no | Compute region (compute/region) |
On success the response is the created GcloudConfigSummary with 201. A duplicate name returns 409; a missing gcloud binary returns 503 with code gcloud_not_found; invalid input returns 400 with a specific code (for example gcloud_config_invalid_name).
Endpoint summary
Section titled “Endpoint summary”| Method & path | Auth | Purpose |
|---|---|---|
GET /api/cost | none | Cost summary and budget status |
POST /api/cost/usage | service token | Ingest sidecar token usage into the ledger |
GET /api/audit | bearer | Recent audit events (filterable) |
GET /api/audit/verify | bearer | Verify the audit hash chain |
GET /api/clawhub/search | bearer | Search ClawHub skills |
GET /api/clawhub/trending | bearer | Trending ClawHub skills |
GET /api/clawhub/skills/{slug} | bearer | ClawHub skill detail |
POST /api/clawhub/install/{slug} | bearer | Install a skill into local Kumiho |
GET /api/auth/profiles | bearer | List credential profile metadata |
POST /api/auth/profiles | bearer | Create a static-token credential profile |
DELETE /api/auth/profiles/{id} | bearer | Delete a credential profile |
POST /api/auth/profiles/{id}/resolve | service token | Decrypt and return a credential |
GET /api/gcloud/configs | bearer | List gcloud configuration profiles |
POST /api/gcloud/configs | bearer | Create a gcloud configuration profile |
Related pages
Section titled “Related pages”- Gateway API overview — transports, auth model, and route conventions
- Status, health, config & tools endpoints — health, doctor, metrics, and config
- Cost tracking & budgets — the
[cost]configuration reference - Observability & tracing — metrics backends and the cost ledger
- Audit log — chain format, signing, and CLI verification
- Security model — bearer vs. service tokens and the secret store
- Workflows & Architect API — where auth profiles are consumed by steps