Engineering handoff
host39 integration guide
Tier 1–3 wiring, API contracts, credentials, and pilot runbook. API base: https://api.rora.carmel.so
Start here
host39 engineering — Tier 1 first.
Immediate next steps
- Wire Tier 1 checkbox → POST webhook (we send NANDA_WEBHOOK_SECRET)
- Render response.reliability in AgentFacts / card UI
- Use GET …/registration as fallback if webhook times out
- Add report_url link (Tier 2) — ready when Tier 1 works
host39 users should not create Agent Status accounts at register time. Implement tiers in order: Tier 1 first, then add Tier 2 links, then Tier 3 for power users. Checklists show a progress bar plus Done / Todo badges for each item. The pilot runbook below is the engineering path to Tier 1 — resolve smoke test first, then webhook wiring.
Integration tiers
Tier 1 → 2 → 3.
Tier 1
Agent Status shipped — waiting on host39 wiringReliability field in host39 (no Agent Status login)
User checks “Enable reliability monitoring” on host39. host39 fires webhook; Agent Status starts probes. host39 polls status API and renders verdict + uptime in the card / AgentFacts reliability field. User never leaves host39.
host39 — todos
0 / 6 done- TodoAdd checkbox on card publish: “Enable reliability monitoring”
- TodoOn save → POST …/webhooks/card-published (event: monitoring.enabled, enable_monitoring: true)
- TodoFallback: poll GET …/registration until monitoring_state = active
- TodoPoll GET …/status every 1–5 min → copy response.reliability into AgentFacts
- TodoOptional: POST …/agents/validate on checkbox toggle (preview before save)
- TodoConfirm probe auth + send 10–20 pilot URNs
Agent Status — todos
5 / 5 done- DoneWebhook onboard + monitoring.disabled pause
- DoneRegistration + status APIs with reliability block
- DonePre-flight validate endpoint (POST …/agents/validate)
- Done26 automated tests passing
- DoneLive on api.rora.carmel.so + secrets shared with host39
APIs
- POST https://api.rora.carmel.so/api/partner/nanda/webhooks/card-published
- POST https://api.rora.carmel.so/api/partner/nanda/agents/validate
- GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/registration
- GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/status
reliability field (copy into AgentFacts)
{
"provider": "agentstatus.dev",
"monitoring": "active",
"verdict": "UP",
"status": "operational",
"uptime_pct": 98.2,
"pass_rate": 0.94,
"last_checked_at": "2026-07-06T17:30:00Z",
"reliability_label": "Operational",
"report_url": "https://agentstatus.dev/nanda/status?locator=...",
"badge_url": "https://api.rora.carmel.so/.../badge.svg",
"claim_url": "https://agentstatus.dev/nanda/claim?token=..."
}Tier 2
Agent Status shipped — host39 adds linksPublic report + badge embed
host39 shows “View full report” (report_url) and optional SVG badge. User opens read-only report — no login.
host39 — todos
0 / 3 done- TodoAdd link: reliability.report_url → “View full report”
- TodoOptional embed: <script src="https://api.rora.carmel.so/static/widget/nanda_badge.js" data-locator="…">
- TodoOptional: <img src="reliability.badge_url"> in card preview
Agent Status — todos
4 / 4 done- DonePublic report page: agentstatus.dev/nanda/status?locator={URN}
- DonePublic status API: GET …/status/public (no auth)
- DoneSVG badge: GET …/badge.svg
- DoneEmbed script: static/widget/nanda_badge.js
APIs
- GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/status/public
- GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/badge.svg
- https://agentstatus.dev/nanda/status?locator={URN}
- https://api.rora.carmel.so/static/widget/nanda_badge.js
Tier 3
Agent Status shipped — host39 adds claim_url linkFull dashboard — opt-in
Power users click claim_url → validate token → open dashboard for alerts, drift, studio. Not required at register.
host39 — todos
0 / 3 done- TodoAdd link: reliability.claim_url → “Manage alerts & metrics”
- TodoPass owner_email in webhook when available
- TodoDo not require Agent Status signup at checkbox time
Agent Status — todos
6 / 6 done- DoneClaim token issued on webhook create (30-day expiry)
- DoneGET …/claim/{token} — validate + dashboard URLs
- DoneClaim landing: agentstatus.dev/nanda/claim?token=…
- DonePOST …/claim/{token}/redeem + GET …/start magic handoff
- DoneAuto-attach on signup/verify via nanda_claim_token
- DoneEmail invite when owner_email provided on webhook
APIs
- claim_url in webhook + status responses
- GET https://api.rora.carmel.so/api/partner/nanda/claim/{token}
- POST https://api.rora.carmel.so/api/partner/nanda/claim/{token}/redeem
- https://agentstatus.dev/nanda/claim?token={token}
- https://platform.agentstatus.dev/rora/agents/{agent_id}
Credentials — who generates what
Quick curl — webhook + status poll
# Tier 1 — enable monitoring (replace SECRET; agent_card_url = live host39 card JSON)
BODY='{"event":"monitoring.enabled","locator":"urn:ai:domain:moonbakery39.com:agent:orders","enable_monitoring":true,"agent_card_url":"https://agentcards.host39.org/moonbakery39.com/orders.json","display_name":"Moon Bakery Orders Agent","status":"active"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$NANDA_WEBHOOK_SECRET" | awk '{print $2}')
curl -sS -w "\nHTTP %{http_code}\n" -X POST "https://api.rora.carmel.so/api/partner/nanda/webhooks/card-published" \
-H "Content-Type: application/json" \
-H "X-Nanda-Signature: sha256=$SIG" \
-d "$BODY" | jq .curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.rora.carmel.so/api/partner/nanda/agents/urn:ai:domain:moonbakery39.com:agent:orders/status" \
-H "X-Nanda-Api-Token: $NANDA_STATUS_API_TOKEN" | jq .Resolve flow
Four hops — live demo URN.
Confirmed against production using urn:ai:domain:moonbakery39.com:agent:orders (May 2026). Same four hops as nandaindex.org/resolve.
NANDA Index resolve
GET https://api.nandaindex.org/api/v1/resolve?locator=urn%3Aai%3Adomain%3Amoonbakery39.com%3Aagent%3Aorders
{
"locator": "urn:ai:domain:moonbakery39.com:agent:orders",
"identifier": "orders",
"index_record": {
"registry_url": "https://agentcards.host39.org/moonbakery39.com/orders.json",
"media_type": "application/a2a-agent-card+json",
"identifier": "urn:ai:domain:moonbakery39.com:agent:orders",
"ttl_seconds": 86400,
"display_name": "Moon Bakery",
"status": "active"
}
}When media_type contains agent-card, registry_url is the direct card JSON URL (host39 SMB path). Re-resolve after ttl_seconds expires.
Agent source (host39)
GET https://agentcards.host39.org/moonbakery39.com/orders.json
{
"name": "Moon Bakery Orders Agent",
"url": "https://time.moonbakery39.com",
"authentication": { "schemes": ["Bearer"] },
"capabilities": { "streaming": false, "pushNotifications": false },
"_meta": {
"identifier": "urn:ai:domain:moonbakery39.com:agent:orders",
"publicUrl": "https://agentcards.host39.org/moonbakery39.com/orders.json",
"hostedBy": "host39.org"
}
}Agent Status reads url as the runtime probe target. name and authentication inform dashboard display and future scoped probes.
Runtime agent card
GET https://time.moonbakery39.com
{
"name": "Time Agent",
"url": "https://time.moonbakery39.com",
"authentication": { "schemes": ["none"] },
"skills": [{
"id": "get-time",
"name": "getTime",
"inputModes": ["text"],
"outputModes": ["text"]
}]
}Optional when hop 2 JSON already contains a usable url. Demo agent serves a time skill at the same base URL.
Runtime invocation (outside-in probe)
POST https://time.moonbakery39.com/run
{
"message": {
"role": "user",
"parts": [{ "type": "text", "text": "what time is it?" }]
}
}Mapped to Rora request_format a2a_jsonrpc. Verdict aggregated across 2,500+ residential probe nodes.
Partner APIs
Agent Status endpoints.
Card published webhook (Tier 1)
host39 → Agent Status
POST https://api.rora.carmel.so/api/partner/nanda/webhooks/card-published
X-Nanda-Signature: sha256=<hmac-sha256-hex of raw body>
{
"event": "monitoring.enabled",
"locator": "urn:ai:domain:moonbakery39.com:agent:orders",
"enable_monitoring": true,
"agent_card_url": "https://agentcards.host39.org/moonbakery39.com/orders.json",
"display_name": "Moon Bakery Orders Agent",
"status": "active",
"owner_email": "owner@example.com"
}202 Accepted — resolves URN, creates/updates monitoring record, returns reliability + claim_url. Also supports monitoring.disabled and card.deleted.
Status + registration (Tier 1 poll)
host39 → Agent Status
GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/status GET https://api.rora.carmel.so/api/partner/nanda/agents/{locator}/registration
X-Nanda-Api-Token: <NANDA_STATUS_API_TOKEN>
// status response includes:
{
"locator": "urn:ai:…",
"verdict": "UP",
"reliability": { "provider": "agentstatus.dev", "uptime_pct": 98.2, ... }
}Use registration as fallback when webhook times out. Poll status every 1–5 min for AgentFacts.
Open with NANDA / host39
- Rate limits on resolve + card fetch at scale
- Probe auth: confirm scoped credentials for pilot URNs
- Enterprise registry hop-2 patterns (/agents/{id} vs AI catalog document)
Dashboard access
Dashboard access — who sees what
Webhook onboarding (Tier 1) does not create Agent Status dashboard logins. The partner APIs are the Tier 1 surface for host39.
Tier 3 claim is per-agent. A separate pilot ops account is required to see the full pilot fleet in one dashboard view.
Acceptance test
Tier 1–3 E2E — how we measure success
Tier 1 is done when steps 1–6 pass on the demo locator (moonbakery39 orders). Tier 2 adds steps 7–8 and 10–12 (public report + host39 links). Tier 3 adds steps 9 and 13–16 (claim → dashboard for one agent). Agent Status ran steps 1–10 and claim API (9) live on 2026-07-06; host39 must pass 11–12 and 16 before Tier 2/3 are declared wired.
Demo locator: urn:ai:domain:moonbakery39.com:agent:orders. Tier 1: steps 1–6 · Tier 2: steps 7–8, 10–12 (11–12 host39) · Tier 3: steps 9, 13–16 (16 host39).
Prerequisites — Agent Status
- Partner API live at api.rora.carmel.so
- Webhook secret + status token shared with host39 (out of band)
Prerequisites — NANDA / host39
- 5–10 pilot URNs with live host39 cards
- Webhook handler with HMAC signature verification
- Status poll or webhook fallback for registration state
NANDA Index resolve
Confirm the locator resolves in the public Index.
LOCATOR="urn:ai:domain:moonbakery39.com:agent:orders"
curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.nandaindex.org/api/v1/resolve?locator=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$LOCATOR'))")" | jq .Verify
- HTTP 200
- Response includes index_record with registry_url or card_url
- Note: Index registry_url may lag card moves — always fetch the live card URL host39 serves (step 2)
Fetch agent card (host39)
Load the published card JSON — this is what Agent Status probes.
curl -sS -w "\nHTTP %{http_code}\n" \
"https://agentcards.host39.org/moonbakery39.com/orders.json" | jq .Verify
- HTTP 200
- Card JSON includes url (runtime base) and name
- Card _meta.identifier matches the URN locator
Runtime smoke (outside-in)
Confirm the runtime accepts an A2A-style invocation.
curl -sS -w "\nHTTP %{http_code}\n" -X POST "https://time.moonbakery39.com/run" \
-H "Content-Type: application/json" \
-d '{"message":{"role":"user","parts":[{"type":"text","text":"what time is it?"}]}}'Verify
- HTTP 200 (or structured auth error — confirms reachability)
- Response body is non-empty JSON
Signed webhook — enable monitoring
Tier 1 onboarding path. host39 fires this on checkbox save.
# Tier 1 — enable monitoring (replace SECRET; agent_card_url = live host39 card JSON)
BODY='{"event":"monitoring.enabled","locator":"urn:ai:domain:moonbakery39.com:agent:orders","enable_monitoring":true,"agent_card_url":"https://agentcards.host39.org/moonbakery39.com/orders.json","display_name":"Moon Bakery Orders Agent","status":"active"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$NANDA_WEBHOOK_SECRET" | awk '{print $2}')
curl -sS -w "\nHTTP %{http_code}\n" -X POST "https://api.rora.carmel.so/api/partner/nanda/webhooks/card-published" \
-H "Content-Type: application/json" \
-H "X-Nanda-Signature: sha256=$SIG" \
-d "$BODY" | jq .Verify
- HTTP 202 Accepted
- Response includes agent_id and reliability block
- reliability.claim_url present when owner_email omitted (Tier 3 opt-in later)
Registration poll (fallback gate)
Use when webhook succeeds but UI needs explicit registration state.
curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.rora.carmel.so/api/partner/nanda/agents/urn:ai:domain:moonbakery39.com:agent:orders/registration" \
-H "X-Nanda-Api-Token: $NANDA_STATUS_API_TOKEN" | jq .Verify
- monitoring_state is registered or active
- probe_state may be pending_first_probe until the first scheduled probe (~60 min)
Status poll → AgentFacts reliability field
Copy response.reliability into host39 card UI every 1–5 minutes.
curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.rora.carmel.so/api/partner/nanda/agents/urn:ai:domain:moonbakery39.com:agent:orders/status" \
-H "X-Nanda-Api-Token: $NANDA_STATUS_API_TOKEN" | jq .Verify
- reliability.provider = agentstatus.dev
- reliability.verdict + reliability_label present
- reliability.report_url and reliability.badge_url populated
Public status API (Tier 2 gate)
No auth — same reliability shape for public report pages.
curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.rora.carmel.so/api/partner/nanda/agents/urn:ai:domain:moonbakery39.com:agent:orders/status/public" | jq .reliabilityVerify
- HTTP 200 without X-Nanda-Api-Token
- reliability block matches authenticated status poll
Badge SVG (Tier 2 gate)
Optional embed; shows Pending until first probe completes.
curl -sS -w "\nHTTP %{http_code}\n" \
"https://api.rora.carmel.so/api/partner/nanda/agents/urn:ai:domain:moonbakery39.com:agent:orders/badge.svg" \
-o /tmp/nanda-badge.svg && head -c 200 /tmp/nanda-badge.svgVerify
- HTTP 200, Content-Type image/svg+xml
- SVG renders verdict label (e.g. Pending, UP)
Claim token validate — API (Tier 3 gate)
API check before the browser claim flow (steps 13–16). Token comes from step 6 reliability.claim_url.
# Copy token from reliability.claim_url (step 6) — URL ends with ?token=…
TOKEN="{claim_token}"
curl -sS "https://api.rora.carmel.so/api/partner/nanda/claim/$TOKEN" | jq .Verify
- valid: true
- Response includes agent_id, signup_url, dashboard_url, redeem_url
- claim_url in status poll matches agentstatus.dev/nanda/claim?token=…
Public report page (Tier 2 gate)
Open reliability.report_url from step 6 in a browser — same page host39 will link to. No login.
# Same URL as reliability.report_url from step 6 — open in browser (no login)
open "https://agentstatus.dev/nanda/status?locator=urn%3Aai%3Adomain%3Amoonbakery39.com%3Aagent%3Aorders"
# Smoke: public report page loads (same URL as reliability.report_url)
curl -sS -o /dev/null -w "HTTP %{http_code}\n" \
"https://agentstatus.dev/nanda/status?locator=urn%3Aai%3Adomain%3Amoonbakery39.com%3Aagent%3Aorders"Verify
- HTTP 200
- Page shows agent name, URN, verdict / reliability label, and last checked time
- URL matches reliability.report_url from the status poll exactly
host39 — “View full report” link (Tier 2 gate)
Wire the card UI to reliability.report_url from the status poll. Link must open the same page as step 10.
{# Use reliability.report_url from status poll — do not hardcode #}
<a href="{{ reliability.report_url }}" target="_blank" rel="noopener noreferrer">
View full report
</a>Verify
- “View full report” visible on the host39 card / AgentFacts surface
- href equals reliability.report_url (not a hardcoded or different URL)
- Opens agentstatus.dev/nanda/status?locator=… in a new tab
host39 — badge on card (Tier 2 optional)
Optional SVG badge next to the reliability field — img from poll or live widget.
{# Option A — img from status poll (simplest) #}
<a href="{{ reliability.report_url }}" target="_blank" rel="noopener noreferrer">
<img src="{{ reliability.badge_url }}" alt="Agent reliability status" height="28" />
</a>
{# Option B — live widget (auto-links to report_url) #}
<script
src="https://api.rora.carmel.so/static/widget/nanda_badge.js"
data-locator="urn:ai:domain:moonbakery39.com:agent:orders"
data-report-url="https://agentstatus.dev/nanda/status?locator=urn%3Aai%3Adomain%3Amoonbakery39.com%3Aagent%3Aorders"
async
></script>Verify
- Badge renders on the host39 card (img or widget)
- Badge label matches current verdict (e.g. Pending, UP, Client error)
- Clicking badge opens the same report URL as step 10
Claim landing page (Tier 3 gate)
Open reliability.claim_url from step 6 in a browser — public claim preview before signup.
# Copy full URL from: jq -r .reliability.claim_url (step 6)
# Shape: https://agentstatus.dev/nanda/claim?token={claim_token}
open "https://agentstatus.dev/nanda/claim?token={claim_token}"Verify
- Page loads without login
- Shows agent name, locator, and current reliability label
- Continue / signup links present (not a 404 or expired token)
Signup or login with claim handoff
New user: complete signup at platform.agentstatus.dev with nanda_claim_token prefilled. Existing user: log in then redeem.
# From step 9 response — new user signup URL
# platform.agentstatus.dev/auth/signup?source=nanda&claim_token=…&locator=…
# Existing logged-in user — attach agent (Bearer session token)
curl -sS -X POST "https://api.rora.carmel.so/api/partner/nanda/claim/{claim_token}/redeem" \
-H "Authorization: Bearer $RORA_SESSION_TOKEN" | jq .Verify
- Signup verify completes with nanda_claim_token (auto-redeem on verify)
- OR POST …/redeem returns ok: true for an existing session
- Do not use the pilot ops account (nanda@…) for this gate — that sees the fleet, not a single claim
Dashboard — one agent attached
Redeemer's account sees exactly the claimed agent — alerts, drift, full metrics. Not the full pilot fleet.
# From step 9 response — dashboard deep link
# platform.agentstatus.dev/rora/agents/{agent_id}?source=nanda&locator=…
curl -sS "https://platform.agentstatus.dev/api/rora/agents" \
-H "Authorization: Bearer $RORA_SESSION_TOKEN" | jq '.agents | map({id, name})'Verify
- Dashboard opens to the claimed agent (agent_id from step 9)
- GET /api/rora/agents lists this agent under the redeemer's rora_signup_id
- Agent does not appear on a different user's account until they claim separately
host39 — claim link + optional owner_email (Tier 3 gate)
Wire claim_url on the card for power users. Pass owner_email on webhook when known for magic invite.
{# Use reliability.claim_url from status poll — not dashboard URL directly #}
<a href="{{ reliability.claim_url }}" target="_blank" rel="noopener noreferrer">
Manage alerts & metrics
</a>
# Webhook — optional owner invite
# Optional on webhook — enables magic invite for card owner (Tier 3)
{
"event": "monitoring.enabled",
"locator": "urn:ai:domain:moonbakery39.com:agent:orders",
"enable_monitoring": true,
"agent_card_url": "https://agentcards.host39.org/moonbakery39.com/orders.json",
"owner_email": "owner@example.com"
}Verify
- “Manage alerts & metrics” (or similar) links to reliability.claim_url
- Tier 1 checkbox still does not require Agent Status signup
- When owner_email is sent, GET …/claim/{token}/start redirects owner toward signup/login with token prefilled
Tier 1 delivery checklist
Agent Status engineering
4 / 5 done- DonePartner API live on api.rora.carmel.so
- DonePartner credentials issued to host39 (webhook secret + status token)
- DoneResolve client + scheduler TTL re-resolve
- DoneTier 2 report + badge and Tier 3 claim flow shipped (API + pages)
- TodoPilot: 10–20 host39 URNs onboarded via Tier 1 webhook
NANDA / host39 delivers
1 / 4 done- TodoTier 1 checkbox → webhook wired
- Todoreliability field rendered in AgentFacts / card UI
- TodoList of pilot URNs with live cards
- DoneDemo URN validated (moonbakery orders)
Success criteria (after E2E passes)
Rollback
- Pause via webhook:
event: monitoring.disabled - Or pause agent:
PATCH /api/rora/agents/{id}→status: paused - No NANDA-side changes required for rollback
Partner API lives on https://api.rora.carmel.so. Webhook URL: POST https://api.rora.carmel.so/api/partner/nanda/webhooks/card-published. Secrets shared out-of-band with host39 engineering.