This repo currently contains two score-schema lineages:
- canonical public/product read surface:
scores - legacy engine/SQLAlchemy lineage:
an_scores+dimension_scores
Unless explicitly noted otherwise, public/product-facing read flows should be understood as reading from scores.
See docs/CANONICAL-SCORE-CONTRACT.md and docs/SCORE-CONTRACT-CONSUMER-AUDIT.md.
Legacy/internal scoring-engine endpoint.
Calculates an AN Score from explicit dimension inputs. In the legacy engine lineage, this path persists score records via the SQLAlchemy-backed scoring layer rather than the canonical public scores read surface.
Request body
{
"service_slug": "stripe",
"dimensions": {
"I1": 9.5,
"I2": 9.0,
"I3": 8.5,
"I4": 9.5,
"I5": 9.0,
"I6": 8.0,
"I7": 9.0,
"F1": 9.0,
"F2": 9.5,
"F3": 9.5,
"F4": 8.5,
"F5": 10.0,
"F6": 9.0,
"F7": 9.0,
"O1": 9.0,
"O2": 9.0,
"O3": 8.0
},
"access_dimensions": {
"A1": 6.0,
"A2": 5.5,
"A3": 6.0,
"A4": 7.5,
"A5": 8.0,
"A6": 8.0
},
"evidence_count": 72,
"freshness": "12 minutes ago",
"probe_types": ["health", "auth", "schema", "load", "idempotency"],
"production_telemetry": true,
"probe_freshness": "18 minutes ago",
"probe_latency_distribution_ms": {"p50": 120, "p95": 340, "p99": 620, "samples": 9},
"hydrate_probe_telemetry": true
}Response body
{
"service_slug": "stripe",
"score": 8.9,
"execution_score": 9.1,
"access_readiness_score": 8.4,
"aggregate_recommendation_score": 8.9,
"an_score_version": "0.2",
"confidence": 0.98,
"tier": "L4",
"tier_label": "Native",
"explanation": "Stripe scores 8.9 because idempotency supports safe retries, but auth flow friction interrupts agent autonomy.",
"dimension_snapshot": {
"dimensions": { "I1": 9.5, "...": 9.0 },
"raw_weights": { "I1": 0.1, "...": 0.03 },
"normalized_weights": { "I1": 0.1, "...": 0.03 },
"category_scores": {
"infrastructure": 8.9,
"interface": 9.1,
"operational": 8.7
}
},
"score_id": "uuid",
"calculated_at": "2026-03-03T22:11:00+00:00"
}hydrate_probe_telemetry is optional. When true, the API auto-hydrates probe_freshness and probe_latency_distribution_ms from the latest stored probe result when those fields are omitted.
In v0.2, score remains a backward-compatible alias of aggregate_recommendation_score.
Fetch the latest persisted score for a service from the current product-facing score surface. For the initial calibration set (stripe, hubspot, sendgrid, resend, github), this route can bootstrap from hand-scored fixtures when no DB row exists yet.
Search indexed services by free-text query. Used by rhumb find <query>.
Response body
{
"data": {
"query": "payment routing",
"results": [
{
"service_slug": "stripe",
"name": "Stripe",
"aggregate_recommendation_score": 8.9,
"tier": "L4",
"confidence": 0.95,
"why": "Best default for payment flows with strong reliability."
}
]
},
"error": null
}limit is optional and can be used by clients to cap result count.
GET /v1/capabilities/{capability_id}/resolve now returns an execute_hint block that is meant to answer the first-success question directly:
preferred_provider: the provider Rhumb wants the operator to use firstselection_reason: machine-readable explanation for why Rhumb chose that provider (highest_ranked_provider,configured_provider_preferred,higher_ranked_provider_unavailable,higher_ranked_provider_not_execute_ready,higher_ranked_provider_mixed_execute_blockers, orhigher_ranked_provider_filtered_by_credential_mode)skipped_provider_slugs: optional higher-ranked providers Rhumb intentionally skipped before choosing the execute path, including providers excluded by a requestedcredential_modeunavailable_provider_slugs: optional subset of skipped higher-ranked providers that are currently breaker-blocked or otherwise unavailable for executenot_execute_ready_provider_slugs: optional subset of skipped higher-ranked providers that still rank but cannot back execute in the current contextauth_method: the request-side credential handle (api_key,connection_ref,crm_ref, etc.)configured: whether that path is already ready on the current deployment in the current context; whencredential_modeis supplied, this is evaluated against that requested mode rather than some other supported modecredential_modes_url: machine-readable handoff to the full per-mode setup matrix for this capabilitypreferred_credential_mode: the lowest-heroics credential mode for that provider in the current contextfallback_providers: optional ordered alternates that can also back execute right now when the preferred path is not the only viable choicesetup_hint: present whenconfigured=false, with the exact next setup action Rhumb expects before executesetup_url: present when Rhumb has a first-class setup surface for that mode, for example a provider ceremony route
fallback_chain stays as the ordered ranked shortlist, but now only includes providers that can actually back execute right now in the current context.
Use GET /v1/capabilities/{capability_id}/credential-modes when you need the full per-mode matrix. Use execute_hint when you want the default next step plus any machine-readable alternates.
If a requested credential_mode filters the provider list down to zero, resolve now keeps the 200 envelope but adds recovery_hint.reason=no_providers_match_credential_mode, resolve_url, credential_modes_url, and the unfiltered supported_provider_slugs / supported_credential_modes so callers can pivot without guessing.
If the capability exists but no providers are registered yet, resolve returns recovery_hint.reason=no_providers_registered plus resolve_url and credential_modes_url so callers can distinguish missing catalog coverage from the other dead-end reasons.
If a requested credential_mode still leaves at least one provider, execute_hint.selection_reason and skipped_provider_slugs now stay honest about any higher-ranked providers that were filtered out, and provider-level plus execute-hint configured truth stays scoped to that requested mode so mixed-mode providers do not look preconfigured through the wrong rail.
If a lower-ranked provider is still execute-ready after higher-ranked paths degrade, execute_hint now keeps the degraded handoff machine-readable too via unavailable_provider_slugs, not_execute_ready_provider_slugs, and the mixed blocker selection reason when both conditions apply.
When a requested credential_mode dead-ends, whether because zero providers match or because the filtered set collapses to zero execute-ready paths, resolve keeps the recovery handoff machine-readable. recovery_hint.resolve_url gives callers the canonical rerun target for the same capability, supported_provider_slugs and supported_credential_modes still reflect the broader unfiltered pivot, recovery_hint.alternate_execute_hint carries the exact broader-rail execute/setup handoff when Rhumb can already identify one, and recovery_hint.setup_handoff carries the next ranked setup action when no executable alternate rail exists yet, so callers can still move forward without another blind search. In the degraded-but-still-ranked case, resolve also keeps the ranked providers list while returning fallback_chain=[], execute_hint=null, and recovery_hint.reason=no_execute_ready_providers plus degraded-provider context like unavailable_provider_slugs and not_execute_ready_provider_slugs.
Example recovery payload when credential_mode=agent_vault dead-ends for email.send but Rhumb can already point you at the broader byok rail:
{
"data": {
"capability": "email.send",
"providers": [],
"fallback_chain": [],
"related_bundles": [],
"execute_hint": null,
"recovery_hint": {
"reason": "no_providers_match_credential_mode",
"requested_credential_mode": "agent_vault",
"resolve_url": "/v1/capabilities/email.send/resolve",
"credential_modes_url": "/v1/capabilities/email.send/credential-modes",
"supported_provider_slugs": ["resend", "sendgrid"],
"supported_credential_modes": ["byok"],
"alternate_execute_hint": {
"preferred_provider": "resend",
"endpoint_pattern": "POST /emails",
"auth_method": "api_key",
"configured": false,
"credential_modes": ["byok"],
"credential_modes_url": "/v1/capabilities/email.send/credential-modes",
"preferred_credential_mode": "byok",
"selection_reason": "highest_ranked_provider",
"fallback_providers": ["sendgrid"],
"setup_hint": "Set RHUMB_CREDENTIAL_RESEND_API_KEY environment variable or configure via proxy credentials"
}
}
},
"error": null
}endpoint_pattern keeps the canonical method-plus-path shape (POST /emails, not just /emails), and /v2/capabilities/{capability_id}/resolve returns the same recovery structure with top-level resolve_url plus nested credential_modes_url values rewritten onto /v2.
Example recovery payload when providers still rank but none can execute yet and Rhumb can only hand you the next setup step:
{
"data": {
"capability": "email.send",
"providers": [
{
"service_slug": "resend",
"recommendation": "preferred",
"credential_modes": ["byok"]
},
{
"service_slug": "sendgrid",
"recommendation": "available",
"credential_modes": ["byok"]
}
],
"fallback_chain": [],
"related_bundles": [],
"execute_hint": null,
"recovery_hint": {
"reason": "no_execute_ready_providers",
"resolve_url": "/v1/capabilities/email.send/resolve",
"credential_modes_url": "/v1/capabilities/email.send/credential-modes",
"supported_provider_slugs": ["resend", "sendgrid"],
"supported_credential_modes": ["byok"],
"setup_handoff": {
"preferred_provider": "resend",
"auth_method": "api_key",
"configured": false,
"credential_modes": ["byok"],
"credential_modes_url": "/v1/capabilities/email.send/credential-modes",
"preferred_credential_mode": "byok",
"selection_reason": "highest_ranked_provider",
"setup_hint": "Set RHUMB_CREDENTIAL_RESEND_API_KEY environment variable or configure via proxy credentials"
},
"not_execute_ready_provider_slugs": ["resend", "sendgrid"]
}
},
"error": null
}Use alternate_execute_hint when Rhumb already knows a live broader execute path. Use setup_handoff when the best next move is still setup rather than execution.
Rhumb now exposes three direct PostgreSQL read-first capabilities:
db.query.readdb.schema.describedb.row.get
These run through the normal capability surface:
GET /v1/capabilities/{capability_id}GET /v1/capabilities/{capability_id}/resolveGET /v1/capabilities/{capability_id}/credential-modesPOST /v1/capabilities/{capability_id}/execute
For hosted Rhumb, the only blessed DB credential path is credential_mode="agent_vault".
agent_vault= preferred: pass a short-lived signedrhdbv1.DB vault token inX-Agent-Token; compatibility fallback: pass a transient PostgreSQL DSN directly inX-Agent-Token, never stored by Rhumbbyok= env-backedconnection_refresolution viaRHUMB_DB_<REF>on the server, intended for self-hosted/internal operator-controlled deployments only
Hosted env-backed connection_ref mode is intentionally disabled/hidden right now. If you are calling the hosted product, use agent_vault.
Execute a bounded, read-only SQL query against the caller's PostgreSQL database.
Use scripts/build_db_agent_vault_token.py to mint a short-lived signed rhdbv1. token bound to a connection_ref and, optionally, an agent_id / org_id.
python3 scripts/build_db_agent_vault_token.py \
--connection-ref conn_app_read \
--dsn 'postgresql://user:pass@db.example.com:5432/app' \
--agent-id agent_123 \
--org-id org_456The script reads the signing secret from RHUMB_DB_AGENT_VAULT_SECRET first, then AUTH_JWT_SECRET / RHUMB_ADMIN_SECRET as fallbacks, matching the DB execute runtime.
Example request (hosted / agent_vault)
curl -X POST http://localhost:8000/v1/capabilities/db.query.read/execute \
-H "Content-Type: application/json" \
-H "X-Agent-Token: rhdbv1.eyJ..." \
-d '{
"credential_mode": "agent_vault",
"connection_ref": "conn_app_read",
"query": "select id, email from users order by created_at desc limit 5"
}'Example response body
{
"data": {
"capability_id": "db.query.read",
"credential_mode": "agent_vault",
"provider_used": "postgresql",
"row_count": 5,
"rows": [
{"id": "u_123", "email": "ada@example.com"}
]
},
"error": null
}db.schema.describe and db.row.get share the same hosted credential posture and execution endpoint shape.
Rhumb now exposes three direct AWS S3 read-first capabilities:
object.listobject.headobject.get
These run through the normal capability surface:
GET /v1/capabilities/{capability_id}GET /v1/capabilities/{capability_id}/resolveGET /v1/capabilities/{capability_id}/credential-modesPOST /v1/capabilities/{capability_id}/execute
For the first S3 slice, Rhumb supports credential_mode="byok" only.
- expected request handle:
storage_ref - runtime resolution: env-backed
RHUMB_STORAGE_<REF>bundle on the server - current posture: operator-controlled / self-hosted style proofing until a cleaner hosted vault shape exists
- bounded public AWS buckets can also use
auth_mode: "anonymous"for unsigned reads while still enforcing explicit bucket/prefix allowlists
Bundle shape:
{
"provider": "aws-s3",
"auth_mode": "access_key",
"aws_access_key_id": "AKIA...",
"aws_secret_access_key": "...",
"region": "us-west-2",
"endpoint_url": "https://<optional-s3-compatible-endpoint>",
"allowed_buckets": ["docs-bucket"],
"allowed_prefixes": {
"docs-bucket": ["reports/"]
}
}endpoint_url is optional. Use it only when you need an explicit S3-compatible endpoint override in a bounded operator-proof environment.
auth_mode is optional and defaults to "access_key". Set "anonymous" only for bounded public AWS proof targets where unsigned reads are intentional.
Use scripts/build_s3_storage_bundle.py to generate and validate that bundle against the product runtime parser before setting it on Railway:
AWS_ACCESS_KEY_ID=... \
AWS_SECRET_ACCESS_KEY=... \
AWS_REGION=us-west-2 \
python3 scripts/build_s3_storage_bundle.py \
--storage-ref st_docs \
--bucket docs-bucket \
--prefix docs-bucket=reports/ \
--railwayThat prints the exact railway variables --set ... command for RHUMB_STORAGE_ST_DOCS.
For a bounded public AWS proof target, use --anonymous instead of access-key env vars:
python3 scripts/build_s3_storage_bundle.py \
--storage-ref st_docs \
--anonymous \
--region us-east-1 \
--bucket 1000genomes \
--prefix 1000genomes=1000G_2504_high_coverage/additional_698_related/ \
--railwayList objects within an allowlisted bucket/prefix.
Example request
curl -X POST http://localhost:8000/v1/capabilities/object.list/execute \
-H "Content-Type: application/json" \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
-d '{
"credential_mode": "byok",
"storage_ref": "st_docs",
"bucket": "docs-bucket",
"prefix": "reports/",
"max_keys": 10
}'Fetch metadata for one allowlisted object.
Fetch a bounded object body for one allowlisted object.
Use scripts/s3_read_dogfood.py for the full hosted proof bundle:
python3 scripts/s3_read_dogfood.py \
--storage-ref st_docs \
--bucket docs-bucket \
--prefix reports/ \
--key reports/daily.json \
--summary-only \
--json-out artifacts/aud18-s3-hosted-proof-<timestamp>.jsonRhumb now exposes three direct Zendesk ticket read-first capabilities:
ticket.searchticket.getticket.list_comments
These run through the normal capability surface:
POST /v1/capabilities/ticket.search/executePOST /v1/capabilities/ticket.get/executePOST /v1/capabilities/ticket.list_comments/execute
For the first Zendesk slice, only credential_mode="byok" is supported.
Requests must include a support_ref that resolves on the server to an env-backed bundle:
RHUMB_SUPPORT_<REF>
Bundle shape:
{
"provider": "zendesk",
"subdomain": "acme",
"auth_mode": "api_token",
"email": "operator@example.com",
"api_token": "zd_api_token",
"allowed_group_ids": [12345],
"allowed_brand_ids": [67890],
"allow_internal_comments": false
}Bearer-token mode is also supported by the runtime parser:
{
"provider": "zendesk",
"subdomain": "acme",
"auth_mode": "bearer_token",
"bearer_token": "zd_bearer_token",
"allowed_group_ids": [12345],
"allowed_brand_ids": [67890],
"allow_internal_comments": false
}- Audit local proof sources plus hosted support-surface state before claiming a hosted bundle exists:
python3 scripts/audit_support_proof_sources.py --provider zendesk --summary-only
- Build and validate the bundle:
python3 scripts/build_zendesk_support_bundle.py --support-ref st_zd --subdomain acme --auth-mode api_token --email you@example.com --api-token "$ZD_API_TOKEN" --allowed-group-id 12345 --allowed-brand-id 67890 --railway
- Run the hosted proof loop once the bundle is set:
python3 scripts/zendesk_read_dogfood.py --support-ref st_zd --ticket-id 123 --comments-ticket-id 123 --denied-ticket-id 456
curl -X POST http://localhost:8000/v1/capabilities/ticket.get/execute \
-H "Content-Type: application/json" \
-H "X-Rhumb-Key: $RHUMB_API_KEY" \
-d '{
"credential_mode": "byok",
"support_ref": "st_zd",
"ticket_id": 12345
}'The runtime enforces:
- group/brand scope via the
support_refbundle - public-comments-only by default
- bounded search and comment limits
- honest provider attribution as
zendesk
Returns Rhumb's current machine-readable public pricing contract.
Response body
{
"data": {
"pricing_version": "2026-03-18",
"canonical_api_base_url": "https://api.rhumb.dev/v1",
"free_tier": {
"included_executions_per_month": 1000
},
"modes": {
"rhumb_managed": {
"margin_percent": 20
},
"x402": {
"margin_percent": 15,
"network": "Base",
"token": "USDC"
},
"byok": {
"upstream_passthrough": true,
"margin_percent": 0
}
}
},
"error": null
}The pricing contract intentionally omits unfinished volume-discount tiers.
Run and persist one internal probe.
Example:
{
"service_slug": "stripe",
"probe_type": "schema",
"target_url": "https://status.stripe.com/api/v2/status.json",
"sample_count": 3,
"trigger_source": "internal"
}Execute a batch run from seed specs (Stripe/OpenAI/HubSpot).
Example:
{
"service_slugs": ["stripe", "openai"],
"sample_count": 3,
"base_interval_minutes": 30,
"dry_run": false
}Response includes cadence_by_service guardrails with:
base_interval_minutes(clamped to a minimum of 5 and maximum of 1440)next_interval_minutes(failure-aware exponential backoff)consecutive_failuresjitter_seconds(deterministic per service)
Fetch the latest persisted probe result for a service (optional probe_type query param).
For probe_type=schema, metadata includes schema_signature_version=v2 and schema_fingerprint_v2, which are derived from nested response shape descriptors (semantic drift guardrail beyond top-level key lists).
Fetch probe-derived drift alerts.
Current primitive alert types:
schema_drift— latest schema fingerprint differs from previous schema probelatency_regression— p95 health latency regressed beyond threshold versus previous probe
Optional query params:
limit(default 50, max 100)