chore: add missing source modules to version control
Deploy to Production / deploy (push) Failing after 7s
Deploy to Production / deploy (push) Failing after 7s
apix-demo, apix-portal/src, apix-spider/src, apix-registry/src, apix-common/src were never staged. Without them the CI build has no source to compile and the Docker images cannot be produced. Also adds docs/ (infrastructure notes) missed in prior commits. Co-Authored-By: Mira <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
package org.botstandards.apix.portal.client;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.botstandards.apix.common.SandboxDashboardResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@RegisterRestClient(configKey = "registry")
|
||||
@Path("/sandbox")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public interface RegistryClient {
|
||||
|
||||
@GET
|
||||
@Path("/{uuid}")
|
||||
SandboxDashboardResponse getDashboard(@PathParam("uuid") String uuid);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package org.botstandards.apix.portal.resource;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.quarkus.qute.CheckedTemplate;
|
||||
import io.quarkus.qute.TemplateInstance;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.botstandards.apix.common.SandboxDashboardResponse;
|
||||
import org.eclipse.microprofile.rest.client.inject.RestClient;
|
||||
import org.botstandards.apix.portal.client.RegistryClient;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
@Path("/sandbox")
|
||||
public class DashboardResource {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(DashboardResource.class);
|
||||
|
||||
@Inject
|
||||
@RestClient
|
||||
RegistryClient registryClient;
|
||||
|
||||
@Inject
|
||||
ObjectMapper objectMapper;
|
||||
|
||||
@CheckedTemplate
|
||||
static class Templates {
|
||||
static native TemplateInstance dashboard(SandboxDashboardResponse dashboard, String dataJson);
|
||||
static native TemplateInstance notFound(String uuid);
|
||||
static native TemplateInstance error(String uuid, String message);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/{uuid}")
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
public TemplateInstance dashboard(@PathParam("uuid") String uuid) {
|
||||
SandboxDashboardResponse dashboard;
|
||||
try {
|
||||
dashboard = registryClient.getDashboard(uuid);
|
||||
} catch (WebApplicationException e) {
|
||||
int status = e.getResponse().getStatus();
|
||||
if (status == 404) return Templates.notFound(uuid);
|
||||
if (status == 400) return Templates.notFound(uuid);
|
||||
LOG.errorf("Registry error fetching sandbox %s: HTTP %d", uuid, status);
|
||||
return Templates.error(uuid, "Registry unavailable");
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Failed to fetch sandbox %s from registry", uuid);
|
||||
return Templates.error(uuid, "Could not reach the registry");
|
||||
}
|
||||
|
||||
try {
|
||||
// Jackson produces safe JSON; replace </ to prevent </script> injection
|
||||
String raw = objectMapper.writeValueAsString(dashboard);
|
||||
String safe = raw.replace("</", "<\\/");
|
||||
return Templates.dashboard(dashboard, safe);
|
||||
} catch (Exception e) {
|
||||
throw new WebApplicationException(Response.serverError()
|
||||
.entity("Failed to serialize dashboard data").build());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,565 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Index — Global Discovery Infrastructure for Autonomous Agents</title>
|
||||
<meta name="description" content="A machine-readable, always-current index of agent-consumable services. The global discovery infrastructure for autonomous agents.">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--black: #0a0a0a;
|
||||
--gray: #4a4a4a;
|
||||
--light: #f5f5f5;
|
||||
--border: #e0e0e0;
|
||||
--accent: #1a1a1a;
|
||||
--mono: "JetBrains Mono", "Fira Code", "Courier New", monospace;
|
||||
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: #ffffff;
|
||||
color: var(--black);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header {
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 1.5rem 2rem;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
header .wordmark {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.95rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--black);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
header .tagline {
|
||||
font-size: 0.8rem;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.25;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.05rem;
|
||||
color: var(--gray);
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 540px;
|
||||
}
|
||||
|
||||
.problem {
|
||||
background: var(--light);
|
||||
border-left: 3px solid var(--black);
|
||||
padding: 1.25rem 1.5rem;
|
||||
margin-bottom: 2.5rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.problem p + p {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gray);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.feature {
|
||||
border: 1px solid var(--border);
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.feature .label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: var(--gray);
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
.feature p {
|
||||
font-size: 0.9rem;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.draft-suite {
|
||||
border: 1px solid var(--border);
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.draft-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1.25rem;
|
||||
padding: 1rem 1.25rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.draft-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.draft-item .label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: var(--gray);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
white-space: nowrap;
|
||||
padding-top: 0.15rem;
|
||||
min-width: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.draft-item .content {
|
||||
font-size: 0.9rem;
|
||||
min-width: 0;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.draft-item .content a {
|
||||
color: var(--black);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.draft-item .content a:hover {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.draft-item .content .sub {
|
||||
font-size: 0.8rem;
|
||||
color: var(--gray);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.draft-item.superseded {
|
||||
background: var(--light);
|
||||
}
|
||||
|
||||
.draft-item.superseded .label {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.draft-item.superseded .content a {
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.status {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.status-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.status-line:first-child {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.dot.done { background: #22c55e; }
|
||||
.dot.active { background: #f59e0b; }
|
||||
.dot.pending { background: var(--border); }
|
||||
|
||||
.status-line .item { flex: 1; }
|
||||
.status-line .phase {
|
||||
font-size: 0.75rem;
|
||||
color: var(--gray);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.contact {
|
||||
font-size: 0.875rem;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.contact a {
|
||||
color: var(--black);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
footer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 1.25rem 2rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--gray);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--gray);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header { flex-direction: column; gap: 0.25rem; }
|
||||
h1 { font-size: 1.35rem; }
|
||||
main { padding: 2.5rem 1.25rem; }
|
||||
.draft-block { flex-direction: column; gap: 0.5rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<a class="wordmark" href="/">api-index.org</a>
|
||||
<span class="tagline">open service registry for autonomous agents</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
<h1>The global discovery infrastructure<br>for autonomous agents.</h1>
|
||||
|
||||
<p class="lead">
|
||||
Autonomous agents cannot reliably find the services they need.
|
||||
The internet was built for humans — its discovery infrastructure
|
||||
assumes a human reading a screen. Agents are navigating it blind.
|
||||
</p>
|
||||
|
||||
<div class="problem">
|
||||
<p>
|
||||
The API Index is a single, globally queryable, machine-readable
|
||||
index of agent-consumable API services — with a structured trust model,
|
||||
capability-based search, and a stable entry point any agent can start from.
|
||||
</p>
|
||||
<p>
|
||||
Discovery is always free for consuming agents. The index is governed by
|
||||
the <a href="https://botstandards.org" target="_blank" rel="noopener noreferrer">Bot Standards Foundation</a>, a neutral
|
||||
non-profit Swiss Stiftung.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h2>What the index provides</h2>
|
||||
|
||||
<div class="features">
|
||||
<div class="feature">
|
||||
<div class="label">Single entry point</div>
|
||||
<p>One stable global URL. Any agent navigates the full index
|
||||
from here via HATEOAS hypermedia links — no prior knowledge required.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<div class="label">Capability search</div>
|
||||
<p>Find services by what they do — not by name or URL.
|
||||
Structured taxonomy from <code>data.legal</code> to <code>nlp</code>
|
||||
to <code>iot</code> and beyond.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<div class="label">Three-dimensional trust model</div>
|
||||
<p>Verified organisation identity · Automated service verification ·
|
||||
Continuous liveness monitoring. Agents apply their own trust policy
|
||||
against verifiable metadata.</p>
|
||||
</div>
|
||||
<div class="feature">
|
||||
<div class="label">Open standard</div>
|
||||
<p>APIX Manifest (APM) supports OpenAPI, MCP, AsyncAPI, and
|
||||
GraphQL. Published under open licence. No proprietary formats.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>IETF Internet-Drafts</h2>
|
||||
|
||||
<div class="draft-suite">
|
||||
<div class="draft-item">
|
||||
<span class="label">Core</span>
|
||||
<div class="content">
|
||||
<a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-core/" target="_blank" rel="noopener noreferrer">
|
||||
draft-rehfeld-apix-core
|
||||
</a>
|
||||
<div class="sub">Core infrastructure, trust model, Index API, operator governance · April 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">Services</span>
|
||||
<div class="content">
|
||||
<a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-services/" target="_blank" rel="noopener noreferrer">
|
||||
draft-rehfeld-apix-services
|
||||
</a>
|
||||
<div class="sub">Web API and bot service registration profile, capability taxonomy, notification channels · April 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">IoT</span>
|
||||
<div class="content">
|
||||
<a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-iot/" target="_blank" rel="noopener noreferrer">
|
||||
draft-rehfeld-apix-iot
|
||||
</a>
|
||||
<div class="sub">IoT device class and instance registration, presence signalling, agent delegation · April 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item superseded">
|
||||
<span class="label">Superseded</span>
|
||||
<div class="content">
|
||||
<a href="https://datatracker.ietf.org/doc/draft-rehfeld-bot-service-index/" target="_blank" rel="noopener noreferrer">
|
||||
draft-rehfeld-bot-service-index
|
||||
</a>
|
||||
<div class="sub">Supersession notice for prior revision · redirects to the three drafts above</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Current status</h2>
|
||||
|
||||
<div class="status">
|
||||
<div class="status-line">
|
||||
<span class="dot done"></span>
|
||||
<span class="item">Internet-Draft submitted to IETF</span>
|
||||
<span class="phase">Phase 0</span>
|
||||
</div>
|
||||
<div class="status-line">
|
||||
<span class="dot done"></span>
|
||||
<span class="item">IETF Dispatch posted · community engagement underway · May 2026</span>
|
||||
<span class="phase">Phase 0</span>
|
||||
</div>
|
||||
<div class="status-line">
|
||||
<span class="dot active"></span>
|
||||
<span class="item">Bot Standards Foundation incorporation underway</span>
|
||||
<span class="phase">Phase 1</span>
|
||||
</div>
|
||||
<div class="status-line">
|
||||
<span class="dot pending"></span>
|
||||
<span class="item">Founding member programme open</span>
|
||||
<span class="phase">Phase 2</span>
|
||||
</div>
|
||||
<div class="status-line">
|
||||
<span class="dot done"></span>
|
||||
<span class="item">Reference implementation live · public sandbox available</span>
|
||||
<span class="phase">Phase 3</span>
|
||||
</div>
|
||||
<div class="status-line">
|
||||
<span class="dot done"></span>
|
||||
<span class="item">Index live at api-index.org · May 2026</span>
|
||||
<span class="phase">Phase 3</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Try it</h2>
|
||||
|
||||
<div class="draft-suite" style="margin-bottom:2.5rem">
|
||||
<div class="draft-item">
|
||||
<span class="label">API root</span>
|
||||
<div class="content">
|
||||
<a href="https://api-index.org/" target="_blank" rel="noopener noreferrer">api-index.org/</a>
|
||||
<div class="sub">HATEOAS entry point — any agent or HTTP client can start here.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">Sandbox</span>
|
||||
<div class="content">
|
||||
<code>POST api-index.org/sandbox/register</code>
|
||||
<div class="sub">Register an isolated test namespace. No KYC. Returns a sandbox UUID and API key immediately. Name is a display label — not unique, not used for routing. Body: <code>{"name":"my-agent","contactEmail":"you@example.com"}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">OpenAPI</span>
|
||||
<div class="content">
|
||||
<a href="https://api-index.org/q/openapi" target="_blank" rel="noopener noreferrer">api-index.org/q/openapi</a>
|
||||
<div class="sub">Full OpenAPI specification for the registry API.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>How to integrate APIX into your agent</h2>
|
||||
|
||||
<div class="problem" style="margin-bottom:1.5rem">
|
||||
<p>
|
||||
APIX is designed so that a well-instructed agent can navigate it without
|
||||
any prior knowledge — starting from a single URL and following hypermedia
|
||||
links. The minimal system prompt addition is one sentence:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="draft-suite" style="margin-bottom:1rem">
|
||||
<div class="draft-item" style="background:#f9f9f9">
|
||||
<span class="label">Prompt</span>
|
||||
<div class="content" style="font-family:'JetBrains Mono','Fira Code','Courier New',monospace;font-size:0.8rem;line-height:1.6;color:#1a1a1a">
|
||||
When you need an external service, first query the API Index at
|
||||
https://api-index.org/ — follow _links.servicesSearch with a
|
||||
?capability= parameter that describes what you need.
|
||||
If you use a sandbox, submit feedback at
|
||||
_links.sandbox + /feedback after each session.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="font-size:0.85rem;color:var(--gray);margin-bottom:2.5rem">
|
||||
That is the full integration for a consuming agent. No SDK, no API key, no registration required.
|
||||
The HATEOAS root tells the agent where everything is; the agent does not need to be told in advance.
|
||||
</p>
|
||||
|
||||
<h2>The Bot Service Manifest (BSM)</h2>
|
||||
|
||||
<p style="font-size:0.875rem;color:var(--gray);margin-bottom:1.25rem">
|
||||
Every registered service carries a structured manifest that agents read to decide
|
||||
whether to invoke a service. The fields are designed to be self-describing —
|
||||
the OpenAPI schema at <a href="https://api-index.org/q/openapi" style="color:var(--black)" target="_blank" rel="noopener noreferrer">api-index.org/q/openapi</a>
|
||||
contains full field descriptions that an agent can retrieve and reason over without
|
||||
being told what each field means in its system prompt.
|
||||
</p>
|
||||
|
||||
<div class="draft-suite" style="margin-bottom:1rem">
|
||||
<div class="draft-item">
|
||||
<span class="label">capabilities</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">Structured taxonomy strings — the primary search key. Examples: <code>nlp.translation</code>, <code>iot.telemetry</code>, <code>data.legal</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">endpoint</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">The service URL an agent calls. Must be HTTPS. The liveness spider checks this continuously.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">openApiSpecUrl</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">URL of the OpenAPI specification. An agent fetches and parses this to learn the service's operations, parameters, and schemas — no human documentation required.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">mcpSpecUrl</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">URL of the MCP server manifest. Equivalent machine-readable contract for agents using the Model Context Protocol. Either field — or both — may be present.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">serviceStage</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem"><code>PRODUCTION</code> · <code>BETA</code> · <code>EXPERIMENTAL</code> · <code>DEPRECATED</code> · <code>DECOMMISSIONED</code>. Agents should filter by stage based on the risk tolerance of their task.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">O-level</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">Operator verification tier: O-0 (unverified) through O-5 (independently audited). Agents apply their own trust policy against this score.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">extensions</span>
|
||||
<div class="content">
|
||||
<div class="sub" style="color:var(--black);font-size:0.875rem">Free-form key/value properties for information not covered by standard fields. Query with <code>?property=key:value</code>. Examples: <code>region:eu</code>, <code>dataResidency:DE</code>, <code>agentFramework:langchain</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style="font-size:0.85rem;color:var(--gray);margin-bottom:2.5rem">
|
||||
The <code>extensions</code> field exists for cases where the standard fields are not sufficient.
|
||||
If your agents consistently use extensions for information that should be a first-class field,
|
||||
that is a signal that the BSM schema needs to evolve.
|
||||
The feedback mechanism exists precisely to collect this signal.
|
||||
</p>
|
||||
|
||||
<h2>The feedback loop</h2>
|
||||
|
||||
<div class="problem" style="margin-bottom:1.5rem">
|
||||
<p>
|
||||
APIX is a living standard. The feedback endpoint at
|
||||
<code>/sandbox/{name}/feedback</code> lets agents rate their experience —
|
||||
discovery accuracy, liveness signal quality, error message clarity,
|
||||
and whether standard BSM fields were sufficient or custom extensions
|
||||
had to fill the gap.
|
||||
</p>
|
||||
<p style="margin-top:0.75rem">
|
||||
<strong>The design intent:</strong> a well-built agent navigates to the feedback schema
|
||||
at <code>_links.feedbackSchema</code> from the index root, reads the dimensions,
|
||||
and submits feedback autonomously after each sandbox session —
|
||||
without being explicitly told to do so.
|
||||
If your agent does not do this automatically, add the feedback URL
|
||||
to its instructions. Low scores on <code>extension_property_coverage</code>
|
||||
are the clearest signal that the BSM schema has a gap.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="draft-suite" style="margin-bottom:2.5rem">
|
||||
<div class="draft-item">
|
||||
<span class="label">Schema</span>
|
||||
<div class="content">
|
||||
<a href="https://api-index.org/sandbox/feedback-schema" target="_blank" rel="noopener noreferrer">api-index.org/sandbox/feedback-schema</a>
|
||||
<div class="sub">Rated dimensions with question text and scale labels. Agents read this before submitting.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">Submit</span>
|
||||
<div class="content">
|
||||
<code>POST /sandbox/{name}/feedback</code>
|
||||
<div class="sub">No authentication required. Any agent that has used the sandbox can submit. JSON body: <code>{"scores": {"hateoas_navigation": 8, "discovery_accuracy": 7}}</code></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="draft-item">
|
||||
<span class="label">Prompt</span>
|
||||
<div class="content" style="font-family:'JetBrains Mono','Fira Code','Courier New',monospace;font-size:0.8rem;line-height:1.6;color:#1a1a1a">
|
||||
After using an APIX sandbox, fetch
|
||||
https://api-index.org/sandbox/feedback-schema,
|
||||
rate your experience on each dimension (0–10),
|
||||
and POST your scores to /sandbox/{name}/feedback.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="contact">
|
||||
Founding member enquiries and institutional partnerships:
|
||||
<a href="mailto:carsten@botstandards.org" target="_blank" rel="noopener noreferrer">carsten@botstandards.org</a>
|
||||
</p>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<span>API Index · governed by the <a href="https://botstandards.org" target="_blank" rel="noopener noreferrer">Bot Standards Foundation</a></span>
|
||||
<span>IETF · <a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-core/" target="_blank" rel="noopener noreferrer">apix-core</a> · <a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-services/" target="_blank" rel="noopener noreferrer">apix-services</a> · <a href="https://datatracker.ietf.org/doc/draft-rehfeld-apix-iot/" target="_blank" rel="noopener noreferrer">apix-iot</a></span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,7 @@
|
||||
quarkus.http.port=8081
|
||||
quarkus.smallrye-health.root-path=/q/health
|
||||
quarkus.log.level=${LOG_LEVEL:INFO}
|
||||
|
||||
quarkus.rest-client.registry.url=${APIX_REGISTRY_URL:https://api-index.org}
|
||||
quarkus.rest-client.registry.connect-timeout=3000
|
||||
quarkus.rest-client.registry.read-timeout=5000
|
||||
@@ -0,0 +1,295 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{dashboard.name} · APIX Sandbox</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
background: #0d1117;
|
||||
color: #c9d1d9;
|
||||
font-family: 'SF Mono', 'Consolas', 'Fira Code', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
a { color: #58a6ff; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
/* ── Header ── */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #21262d;
|
||||
}
|
||||
.header-logo { color: #8b949e; font-size: 0.8rem; }
|
||||
.header-name { font-size: 1rem; color: #e6edf3; font-weight: 600; }
|
||||
.tier-badge {
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
color: #8b949e;
|
||||
padding: 0.1rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ── Map ── */
|
||||
#map-wrap {
|
||||
background: #060d18;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#world-map {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.map-label {
|
||||
position: absolute;
|
||||
bottom: 0.6rem;
|
||||
right: 0.8rem;
|
||||
font-size: 0.65rem;
|
||||
color: #30363d;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ── Star ── */
|
||||
@keyframes star-glow {
|
||||
0%, 100% { filter: drop-shadow(0 0 3px #ffd700) drop-shadow(0 0 6px #ffd70066); }
|
||||
50% { filter: drop-shadow(0 0 8px #ffd700) drop-shadow(0 0 18px #ffd700aa) drop-shadow(0 0 32px #ffd70033); }
|
||||
}
|
||||
.registrar-star {
|
||||
font-size: 14px;
|
||||
fill: #ffd700;
|
||||
dominant-baseline: central;
|
||||
text-anchor: middle;
|
||||
animation: star-glow 2.4s ease-in-out infinite;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ── Blinks ── */
|
||||
@keyframes blink-pulse {
|
||||
0% { opacity: 0; r: 2; }
|
||||
25% { opacity: 0.9; r: 5; }
|
||||
65% { opacity: 0.4; r: 4; }
|
||||
100% { opacity: 0; r: 2; }
|
||||
}
|
||||
.agent-blink {
|
||||
fill: #3d8bfd;
|
||||
opacity: 0;
|
||||
animation: blink-pulse 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── Stats ── */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1px;
|
||||
background: #21262d;
|
||||
border-top: 1px solid #21262d;
|
||||
}
|
||||
.stat-card {
|
||||
background: #0d1117;
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 0.7rem;
|
||||
color: #484f58;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 1.4rem;
|
||||
color: #e6edf3;
|
||||
font-weight: 600;
|
||||
}
|
||||
.stat-sub {
|
||||
font-size: 0.7rem;
|
||||
color: #484f58;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
/* ── Footer ── */
|
||||
.footer {
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid #21262d;
|
||||
font-size: 0.75rem;
|
||||
color: #484f58;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<span class="header-logo"><a href="/">APIX</a></span>
|
||||
<span class="header-name">{dashboard.name}</span>
|
||||
<span class="tier-badge">{dashboard.tier}</span>
|
||||
</div>
|
||||
|
||||
<div id="map-wrap">
|
||||
<svg id="world-map"></svg>
|
||||
<div class="map-label">agent interaction map</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Services registered</div>
|
||||
<div class="stat-value">{#if dashboard.usage.get('SERVICE_REGISTERED') != null}{dashboard.usage.get('SERVICE_REGISTERED')}{#else}0{/if}</div>
|
||||
<div class="stat-sub">of {#if dashboard.maxServices != null}{dashboard.maxServices}{#else}∞{/if} allowed</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Capability searches</div>
|
||||
<div class="stat-value">{#if dashboard.usage.get('SERVICE_SEARCHED') != null}{dashboard.usage.get('SERVICE_SEARCHED')}{#else}0{/if}</div>
|
||||
<div class="stat-sub">agent discovery calls</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Service list calls</div>
|
||||
<div class="stat-value">{#if dashboard.usage.get('SERVICE_LISTED') != null}{dashboard.usage.get('SERVICE_LISTED')}{#else}0{/if}</div>
|
||||
<div class="stat-sub">full list requests</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Rate limit</div>
|
||||
<div class="stat-value">{dashboard.ratePerMinute}</div>
|
||||
<div class="stat-sub">requests / minute</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Sandbox expires</div>
|
||||
<div class="stat-value" id="expires-val">—</div>
|
||||
<div class="stat-sub" id="expires-sub">{dashboard.expiresAt}</div>
|
||||
</div>
|
||||
{#if dashboard.registrarLocation != null}
|
||||
<div class="stat-card">
|
||||
<div class="stat-label">Registered from</div>
|
||||
<div class="stat-value" style="font-size:1rem;">{dashboard.registrarLocation}</div>
|
||||
<div class="stat-sub">owner-declared location</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>sandbox id: {dashboard.sandboxId}</span>
|
||||
<span><a href="https://api-index.org">api-index.org</a> · APIX Registry</span>
|
||||
</div>
|
||||
|
||||
<!-- Data injected by portal (Jackson-serialised, </script> escaped) -->
|
||||
<script>
|
||||
var __D = {dataJson.raw};
|
||||
</script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js"></script>
|
||||
{#raw}
|
||||
<script>
|
||||
(function () {
|
||||
var data = window.__D || {};
|
||||
var visits = data.recentVisits || [];
|
||||
var hasRegistrar = typeof data.registrarLat === 'number' && typeof data.registrarLon === 'number';
|
||||
|
||||
// ── Expires display ──────────────────────────────────────────────────────
|
||||
var expiresEl = document.getElementById('expires-val');
|
||||
var expiresSub = document.getElementById('expires-sub');
|
||||
if (data.expiresAt && expiresEl) {
|
||||
var d = new Date(data.expiresAt);
|
||||
expiresEl.textContent = d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' });
|
||||
var days = Math.ceil((d - Date.now()) / 86400000);
|
||||
expiresSub.textContent = days > 0 ? days + ' days remaining' : 'expired';
|
||||
}
|
||||
|
||||
// ── Map ──────────────────────────────────────────────────────────────────
|
||||
var wrap = document.getElementById('map-wrap');
|
||||
var svg = d3.select('#world-map');
|
||||
var W = wrap.clientWidth || 800;
|
||||
var H = Math.round(W * 0.48);
|
||||
svg.attr('viewBox', '0 0 ' + W + ' ' + H)
|
||||
.attr('width', W)
|
||||
.attr('height', H);
|
||||
|
||||
var projection = d3.geoNaturalEarth1()
|
||||
.scale(W / 6.3)
|
||||
.translate([W / 2, H / 2]);
|
||||
var path = d3.geoPath(projection);
|
||||
|
||||
// Ocean
|
||||
svg.append('rect')
|
||||
.attr('width', W).attr('height', H)
|
||||
.attr('fill', '#060d18');
|
||||
|
||||
fetch('https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(world) {
|
||||
// Land
|
||||
svg.append('path')
|
||||
.datum(topojson.feature(world, world.objects.countries))
|
||||
.attr('d', path)
|
||||
.attr('fill', '#111820')
|
||||
.attr('stroke', '#1e2a38')
|
||||
.attr('stroke-width', 0.5);
|
||||
|
||||
// Graticule (faint grid)
|
||||
svg.append('path')
|
||||
.datum(d3.geoGraticule()())
|
||||
.attr('d', path)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#0d1a28')
|
||||
.attr('stroke-width', 0.4);
|
||||
|
||||
// Agent blinks — resolve duplicates by approximate cell
|
||||
var cellSize = 1.5; // degrees
|
||||
var cells = {};
|
||||
visits.forEach(function(v) {
|
||||
var cell = Math.round(v.lat / cellSize) + ',' + Math.round(v.lon / cellSize);
|
||||
if (!cells[cell]) cells[cell] = { lat: v.lat, lon: v.lon, count: 0 };
|
||||
cells[cell].count++;
|
||||
});
|
||||
var points = Object.values(cells);
|
||||
points.forEach(function(pt, i) {
|
||||
var pos = projection([pt.lon, pt.lat]);
|
||||
if (!pos) return;
|
||||
var r = Math.min(2 + Math.log1p(pt.count) * 1.5, 8);
|
||||
svg.append('circle')
|
||||
.attr('class', 'agent-blink')
|
||||
.attr('cx', pos[0])
|
||||
.attr('cy', pos[1])
|
||||
.attr('r', r)
|
||||
.style('animation-delay', (i * 190 % 5000) + 'ms');
|
||||
});
|
||||
|
||||
// Registrar star — drawn last so it sits on top
|
||||
if (hasRegistrar) {
|
||||
var pos = projection([data.registrarLon, data.registrarLat]);
|
||||
if (pos) {
|
||||
svg.append('text')
|
||||
.attr('class', 'registrar-star')
|
||||
.attr('x', pos[0])
|
||||
.attr('y', pos[1])
|
||||
.text('★');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function(e) {
|
||||
console.warn('Map load failed:', e);
|
||||
});
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────
|
||||
window.addEventListener('resize', function() {
|
||||
W = wrap.clientWidth || 800;
|
||||
H = Math.round(W * 0.48);
|
||||
svg.attr('viewBox', '0 0 ' + W + ' ' + H)
|
||||
.attr('width', W).attr('height', H);
|
||||
projection.scale(W / 6.3).translate([W / 2, H / 2]);
|
||||
svg.selectAll('path').attr('d', path);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{/raw}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Error · APIX</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0d1117; color: #c9d1d9; font-family: 'SF Mono', 'Consolas', monospace; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.container { text-align: center; max-width: 480px; padding: 2rem; }
|
||||
h1 { font-size: 1.2rem; color: #8b949e; margin-bottom: 1rem; font-weight: 400; }
|
||||
p { font-size: 0.9rem; color: #484f58; margin-bottom: 2rem; }
|
||||
a { color: #58a6ff; text-decoration: none; font-size: 0.85rem; }
|
||||
a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>{message}</h1>
|
||||
<p>Could not load dashboard for sandbox {uuid}.</p>
|
||||
<a href="/">← back</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Sandbox not found · APIX</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { background: #0d1117; color: #c9d1d9; font-family: 'SF Mono', 'Consolas', monospace; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
|
||||
.container { text-align: center; max-width: 480px; padding: 2rem; }
|
||||
h1 { font-size: 1.2rem; color: #8b949e; margin-bottom: 1rem; font-weight: 400; }
|
||||
p { font-size: 0.9rem; color: #484f58; margin-bottom: 2rem; line-height: 1.6; }
|
||||
a { color: #58a6ff; text-decoration: none; font-size: 0.85rem; }
|
||||
a:hover { text-decoration: underline; }
|
||||
code { background: #161b22; padding: 0.15em 0.4em; border-radius: 3px; font-size: 0.8rem; color: #8b949e; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Sandbox not found</h1>
|
||||
<p>No sandbox exists for <code>{uuid}</code>.<br>The sandbox may have expired or the UUID is incorrect.</p>
|
||||
<a href="/">← back to api-index.org</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user