Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/app/api/badge/[slug]/[provider]/snippet/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* Copy-paste embed snippets for the per-(benchmark, provider) badge SVG.
*
* GET /api/badge/<bench>/<provider>/snippet?format=markdown|html|url|json
*
* Returns a ready-to-paste embed code so a provider can drop a live
* "Ranked #N on OpenChainBench" badge into their README, docs page,
* or marketing site without crafting the URL by hand. The badge SVG
* itself still lives at /api/badge/<bench>/<provider>; this endpoint
* only wraps it.
*
* Why this exists separately from the SVG route: it is the one
* surface readers reach for when they want to BACKLINK us. Keeping
* snippet rendering out of the SVG path keeps the SVG cache hot
* (one cacheable shape per benchmark + provider) and avoids polluting
* the SVG content negotiation with a text/* branch.
*
* Optional query params forwarded to the badge URL so a provider can
* embed a scope-restricted badge (chain, region, kind). The site URL
* the badge links to also picks up the same scope where applicable,
* so a reader clicking through lands on the matching variant view.
*/

import { type NextRequest, NextResponse } from "next/server";
import { getBenchmark } from "@/data/benchmarks";
import { clientKey, rateLimit, tooManyRequests } from "@/lib/rate-limit";
import { PROVIDER_RE, SLUG_RE } from "@/lib/slug";
import { SITE } from "@/data/site";

export const revalidate = 600;

type Params = { slug: string; provider: string };

const FORMATS = ["markdown", "html", "url", "json"] as const;
type Format = (typeof FORMATS)[number];

function isFormat(v: string | null): v is Format {
return v != null && (FORMATS as readonly string[]).includes(v);
}

export async function GET(
req: NextRequest,
{ params }: { params: Promise<Params> },
) {
const r = rateLimit(clientKey(req, "badge-snippet"), 120, 60);
if (!r.ok) return tooManyRequests(r.retryAfterSec);

const { slug, provider } = await params;
if (!SLUG_RE.test(slug) || !PROVIDER_RE.test(provider)) {
return NextResponse.json({ error: "invalid_slug" }, { status: 400 });
}

const benchmark = await getBenchmark(slug);
if (!benchmark) {
return NextResponse.json({ error: "bench_not_found" }, { status: 404 });
}
const result = benchmark.results.find((p) => p.slug === provider);
if (!result) {
return NextResponse.json({ error: "provider_not_found" }, { status: 404 });
}

const sp = req.nextUrl.searchParams;
const format: Format = isFormat(sp.get("format")) ? (sp.get("format") as Format) : "markdown";
const chain = sp.get("chain")?.trim() || "";
const region = sp.get("region")?.trim() || "";
const kind = sp.get("kind")?.trim() || "";

const scopeQs = new URLSearchParams();
if (chain) scopeQs.set("chain", chain);
if (region) scopeQs.set("region", region);
if (kind) scopeQs.set("kind", kind);
const scopeSuffix = scopeQs.toString();

const badgeUrl =
`${SITE.url}/api/badge/${slug}/${provider}` +
(scopeSuffix ? `?${scopeSuffix}` : "");
const benchUrl =
`${SITE.url}/benchmarks/${slug}` +
(scopeSuffix ? `?${scopeSuffix}` : "");

const alt = `OpenChainBench ${benchmark.title} ranking for ${result.name}`;

const snippets = {
markdown: `[![${alt}](${badgeUrl})](${benchUrl})`,
html: `<a href="${benchUrl}" target="_blank" rel="noopener"><img src="${badgeUrl}" alt="${alt}" /></a>`,
url: badgeUrl,
} as const;

if (format === "json") {
return NextResponse.json(
{
benchmark: { slug, title: benchmark.title, url: benchUrl },
provider: { slug: provider, name: result.name },
badge_url: badgeUrl,
snippets,
scope: { chain: chain || null, region: region || null, kind: kind || null },
license: "CC-BY-4.0",
},
{
headers: {
"cache-control": "public, s-maxage=300, stale-while-revalidate=600",
},
},
);
}

return new NextResponse(snippets[format], {
headers: {
"content-type": "text/plain; charset=utf-8",
"cache-control": "public, s-maxage=300, stale-while-revalidate=600",
},
});
}
274 changes: 274 additions & 0 deletions src/app/partners/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import Link from "next/link";
import type { Metadata } from "next";
import { pageMetadata } from "@/lib/page-metadata";
import { SITE } from "@/data/site";
import { safeJsonLd, buildBreadcrumbJsonLd } from "@/lib/jsonld";

/**
* Partners and integrations page. Documents the public assets a provider,
* tool, or media outlet can drop into their own surfaces to surface
* OpenChainBench measurements: live badges, share cards, citation
* snippets, and the public data endpoints (REST, OpenAPI, MCP, llms.txt).
*
* Purpose is twofold:
* 1. Reduce friction for sites that already want to cite us. Every
* asset on this page is meant to be copied and dropped in with no
* back-and-forth.
* 2. Surface the CC-BY-4.0 license clearly so legal teams have an easy
* answer when their content team asks "can we embed this badge?".
*/

const DESCRIPTION =
"Live badges, share cards, citation snippets and public data APIs you can drop into your README, docs, or marketing site to surface live OpenChainBench measurements. CC-BY-4.0 licensed.";

export const metadata: Metadata = pageMetadata({
path: "/partners",
title: "Partners and integrations",
description: DESCRIPTION,
});

export const revalidate = 3600;

export default function PartnersPage() {
const breadcrumbLd = {
"@context": "https://schema.org",
...buildBreadcrumbJsonLd([
{ name: "Home", item: SITE.url },
{ name: "Partners", item: `${SITE.url}/partners` },
]),
};

return (
<article className="mx-auto max-w-3xl px-4 sm:px-6 py-10 sm:py-14">
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: serialized via safeJsonLd
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbLd) }}
/>

<p
className="label-mono text-[10px] text-ink-faint mb-2"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
Partners and integrations
</p>
<h1 className="display text-3xl sm:text-4xl text-ink leading-tight">
Embed OpenChainBench measurements anywhere
</h1>
<p className="mt-4 text-base sm:text-lg text-ink-soft leading-snug max-w-2xl">
Every measurement on OpenChainBench is published under the
Creative Commons Attribution 4.0 license. You can mirror, embed,
cite and reformat any value as long as the source link points
back to the benchmark page it came from. The assets below are
the ones we maintain so you do not have to.
</p>

<Section
title="Live ranking badges"
slug="badges"
description="SVG badges that render the live rank + headline figure for one provider on one benchmark. The SVG refreshes on the OpenChainBench CDN every five minutes so an embedded copy stays in sync without any work on your side."
>
<Example
label="Endpoint"
code={`GET https://openchainbench.com/api/badge/<benchmark-slug>/<provider-slug>`}
/>
<Example
label="Direct SVG (example)"
code={`https://openchainbench.com/api/badge/rpc-capabilities/helius`}
/>
<Example
label="Markdown for a README"
code={`[![OpenChainBench rpc-capabilities ranking for Helius](https://openchainbench.com/api/badge/rpc-capabilities/helius)](https://openchainbench.com/benchmarks/rpc-capabilities)`}
/>
<Example
label="HTML embed"
code={`<a href="https://openchainbench.com/benchmarks/rpc-capabilities" target="_blank" rel="noopener"><img src="https://openchainbench.com/api/badge/rpc-capabilities/helius" alt="OpenChainBench rpc-capabilities ranking for Helius" /></a>`}
/>
<p className="mt-3 text-sm text-ink-soft leading-relaxed">
Optional query params <code className="mono-tag">?chain=</code>,
{" "}<code className="mono-tag">?region=</code> and
{" "}<code className="mono-tag">?kind=</code> scope the rank to
a single cell when the benchmark publishes a chain x region
ranking matrix. The badge prints the scope as a subscript so a
reader never reads a chain-scoped #1 as a global finish.
</p>
<p className="mt-2 text-sm text-ink-soft leading-relaxed">
Need a copy-paste string instead of a URL? Append
{" "}<code className="mono-tag">/snippet?format=markdown</code> (or
{" "}<code className="mono-tag">html</code>,{" "}
<code className="mono-tag">url</code>,{" "}
<code className="mono-tag">json</code>) to the badge endpoint
and the server returns the formatted snippet directly.
</p>
</Section>

<Section
title="Share cards (1200 x 630)"
slug="share-cards"
description="High-resolution PNG poster of any benchmark. Five layouts: full ranking, leaderboard, 24h chart snapshot, single-provider headline, two-provider compare. Each is rendered server-side from React, so the values are always the most recent ones the benchmark holds at request time."
>
<Example
label="Endpoint"
code={`GET https://openchainbench.com/benchmarks/<benchmark-slug>/share-card?template=<layout>`}
/>
<p className="mt-3 text-sm text-ink-soft leading-relaxed">
Templates accepted: <code className="mono-tag">ranking</code>,{" "}
<code className="mono-tag">leaderboard</code>,{" "}
<code className="mono-tag">snapshot</code>,{" "}
<code className="mono-tag">headline</code>,{" "}
<code className="mono-tag">compare</code>. Each benchmark page
ships with a Share dialog that previews every template and
downloads the PNG, so you can pick visually if the URL form is
not your preference.
</p>
</Section>

<Section
title="Citation snippets"
slug="citations"
description="Plain-text quotes and pre-formatted headline sentences for journalists, researchers and LLMs that need a one-liner with the source link baked in."
>
<Example
label="Per-benchmark JSON"
code={`GET https://openchainbench.com/api/stat/<benchmark-slug>`}
/>
<Example
label="Full index"
code={`GET https://openchainbench.com/api/citable`}
/>
<p className="mt-3 text-sm text-ink-soft leading-relaxed">
Each <code className="mono-tag">/api/stat</code> response
carries a ready-to-paste headline sentence and a citation quote
with the canonical bench URL embedded. The citation index
covers every live benchmark in a single response so an LLM or
aggregator can ingest the catalog in one call.
</p>
</Section>

<Section
title="Public data APIs"
slug="apis"
description="REST endpoints and an MCP server expose the raw rankings, sparkline series, methodology and provider metadata. Same data the OCB site renders, no scraping required."
>
<Example
label="OpenAPI"
code={`GET https://openchainbench.com/api/openapi.json`}
/>
<Example
label="MCP server (Streamable HTTP)"
code={`https://openchainbench.com/api/mcp/mcp`}
/>
<Example
label="LLM-ready context dump (~80 KB markdown)"
code={`GET https://openchainbench.com/api/llm-context`}
/>
<Example label="llms.txt index" code={`https://openchainbench.com/llms.txt`} />
</Section>

<Section
title="License"
slug="license"
description="Every measurement on this site is published under Creative Commons Attribution 4.0 International. You can mirror, transform, embed and quote the data freely as long as the source link points back to the benchmark page it came from."
>
<p className="text-sm text-ink-soft leading-relaxed">
Full license text:{" "}
<a
className="lnk"
href="https://creativecommons.org/licenses/by/4.0/"
target="_blank"
rel="noopener"
>
creativecommons.org/licenses/by/4.0
</a>
</p>
</Section>

<Section
title="Talk to us"
slug="contact"
description="If you are integrating OpenChainBench in a product, a research note, or a comparison page and want methodology coverage notes, custom variant URLs, or anything more involved, we are happy to coordinate."
>
<p className="text-sm text-ink-soft leading-relaxed">
Email{" "}
<a className="lnk" href="mailto:openchainbench@gmail.com">
openchainbench@gmail.com
</a>{" "}
or DM{" "}
<a
className="lnk"
href="https://x.com/OpenChainBench"
target="_blank"
rel="noopener"
>
@OpenChainBench
</a>{" "}
on X. Source code lives on{" "}
<a
className="lnk"
href="https://github.com/ChainBench/OpenChainBench"
target="_blank"
rel="noopener"
>
GitHub
</a>
.
</p>
</Section>

<p className="mt-12 text-[11px] text-ink-faint italic">
Looking for the full benchmark catalog?{" "}
<Link className="underline" href="/benchmarks">
/benchmarks
</Link>{" "}
lists every live measurement. The{" "}
<Link className="underline" href="/methodology">
methodology
</Link>{" "}
page documents how each headline figure is computed.
</p>
</article>
);
}

function Section({
title,
slug,
description,
children,
}: {
title: string;
slug: string;
description: string;
children?: React.ReactNode;
}) {
return (
<section className="mt-10" id={slug}>
<h2 className="text-lg sm:text-xl font-bold tracking-tight text-ink">
{title}
</h2>
<p className="mt-2 text-sm text-ink-soft leading-relaxed max-w-2xl">
{description}
</p>
<div className="mt-4 space-y-3">{children}</div>
</section>
);
}

function Example({ label, code }: { label: string; code: string }) {
return (
<div>
<p
className="label-mono text-[10px] text-ink-faint mb-1 uppercase tracking-wide"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
{label}
</p>
<pre
className="rounded-md border border-ink/15 bg-paper-soft/40 px-3 py-2 text-[12px] overflow-x-auto"
style={{ fontFamily: "var(--font-mono, monospace)" }}
>
<code>{code}</code>
</pre>
</div>
);
}
1 change: 1 addition & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function staticHubRoutes(catalogTs: Date): MetadataRoute.Sitemap {
{ url: `${SITE.url}/mcp`, lastModified: pageMtime("mcp/page.tsx"), changeFrequency: "monthly", priority: 0.8 },
{ url: `${SITE.url}/methodology`, lastModified: pageMtime("methodology/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
{ url: `${SITE.url}/contribute`, lastModified: pageMtime("contribute/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
{ url: `${SITE.url}/partners`, lastModified: pageMtime("partners/page.tsx"), changeFrequency: "monthly", priority: 0.7 },
{ url: `${SITE.url}/about`, lastModified: pageMtime("about/page.tsx"), changeFrequency: "monthly", priority: 0.5 },
{ url: `${SITE.url}/press`, lastModified: pageMtime("press/page.tsx"), changeFrequency: "monthly", priority: 0.4 },
{ url: `${SITE.url}/compare`, lastModified: pageMtime("compare/page.tsx"), changeFrequency: "weekly", priority: 0.6 },
Expand Down
Loading
Loading