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
16 changes: 16 additions & 0 deletions .github/workflows/check-links.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Documentation links
on:
pull_request:
push:
branches: ['**']
permissions:
contents: read
jobs:
links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: node scripts/check-internal-links.mjs
56 changes: 56 additions & 0 deletions AUDIT_2026-09-11.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Documentation audit — 2026-09-11

## Canonical source and deployment

`aifinpay-docs` is the canonical docs source. It is a Mintlify site configured
by `docs.json`, with the intended custom domain `https://docs.aifinpay.io`.
The connected repository and custom-domain state require verification in the
Mintlify project; this checkout contains no provider-side deployment metadata
and has no local Docker deployment step.
`aifp-docs-work/` is a local archive/work folder and is not a Git checkout.

## Verified findings

- Main-site developer and docs navigation points to `https://docs.aifinpay.io`.
- The active provider entry point is `https://dash.aifinpay.io`; the
`charge/onboarding` page explicitly sends self-serve providers there.
- The legacy registry bridge flow is retained as an assisted path and does not
advertise `/partners/apply`, `/partners/onboard`, or `/partners/publish-api`.
- `quickstart.mdx` linked to the nonexistent `/mcp` and `/integrations` routes.
Those cards now point to the existing `/pay/mcp` and `/integration` pages,
with copy matching what those pages provide.
- The introduction no longer says settlement is live across 12 networks. It
now points to the readiness reference and describes Polygon as the current
SDK settlement path while per-chain routing rolls out.
- The integration guide now separates stateless receipt signature verification
from stateful quota accounting, documents the one-process limit of the
default `MemoryStore`, and requires a shared atomic store for multi-process
deployments. It also tells manual `/v1/pay` clients to follow any
`payment_authorization` requirement present in their quote. The development
receipt-authorization design was not presented as a production rollout.

## Validation

All relative links in the canonical MDX files were checked against the
repository page files, and every page listed by `docs.json` exists. The two
broken links above were the only missing targets found. No payment, API,
contract, or deployment behavior was changed.

`scripts/check-internal-links.mjs` now provides the same dependency-free check
for local or CI use; it passes with 17 configured pages and no missing links.

The actual Mintlify local preview found a missing `/favicon.svg` referenced by
`docs.json`. The docs now reuse the main website's existing `favicon.ico`, and
the checker also verifies configured logo and favicon files. The Quickstart
render and corrected next-step links were checked in the local preview. The
Mintlify GitHub check skipped PR11 with "No eligible deployments found";
this review does not claim that production docs have been updated.

The Quickstart economics wording was also checked against the canonical pricing
reference and the current SDK settlement validators. The canonical AIFP-1
route is gross-inclusive, but the Quickstart example targets a provider bridge
whose challenge defines the actual route; it now describes challenge-defined
terms and links to the canonical reference instead of hardcoding a split on a
generic bridge call. The example no longer prints `secret_b58`; it tells users
to keep a reusable secret in a local secret manager or keypair file and out of
shared logs.
42 changes: 26 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ The AiFinPay developer documentation, built with [Mintlify](https://mintlify.com
— the same platform our competitors use. Role × language structure: pay for
services / charge for your API / reference.

## Go live at docs.aifinpay.io

1. **Push to GitHub.** Put this folder in a repo (e.g. `AiFinPay/docs`).
2. **Connect Mintlify.** Sign up at https://mintlify.com → **Connect GitHub** →
pick the repo (and this subfolder if it isn't the repo root). Mintlify
rebuilds on every push and gives you a preview at `<project>.mintlify.app`.
3. **Custom domain.** Mintlify dashboard → **Settings → Custom domain** →
`docs.aifinpay.io`. Mintlify shows a CNAME target.
4. **DNS (Cloudflare, `aifinpay.io` zone).** Add a CNAME:
`docs` → the value Mintlify gives (usually `cname.mintlify.app`), per
Mintlify's proxied/DNS-only instruction.
5. **Assets.** Drop logo files at `logo/light.svg` + `logo/dark.svg` and
`favicon.svg` (reuse the ones from `ofh-master/src/assets`).

Then point the main-site nav **DOCS** link at `https://docs.aifinpay.io`
(currently it points at the interim React `/docs` hub).
## Source and deployment

This repository is the canonical docs source. The `origin` remote is the
AiFinPay docs repository, whose default branch is `main`; a personal remote is
also configured for mirror or recovery workflows. Work on a branch, review it,
and merge or push through the normal repository workflow.

The intended deployment mechanism is Mintlify at `https://docs.aifinpay.io`.
The connected repository and custom-domain status must be verified in the
Mintlify project; this checkout contains no provider-side deployment metadata.
Do not create a second application deployment or move the docs into another
repository. Mintlify should read `docs.json` and publish its configured page
tree. The main site already links its **DOCS** navigation to this domain, and
its **DASHBOARD** / provider CTAs go to the self-service customer panel at
[dash.aifinpay.io](https://dash.aifinpay.io).

The logo and dark/light assets are already checked in under `logo/`; keep them
in place when editing the site.

## Local preview

Expand All @@ -28,6 +30,14 @@ npm i -g mint
mint dev # http://localhost:3000
```

## Local link check

The repository includes a dependency-free check for local page links:

```bash
node scripts/check-internal-links.mjs
```

## Structure

```
Expand Down
2 changes: 1 addition & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"light": "#83AED8",
"dark": "#0A0E1A"
},
"favicon": "/favicon.svg",
"favicon": "/favicon.ico",
"navigation": {
"tabs": [
{
Expand Down
Binary file added favicon.ico
Binary file not shown.
28 changes: 23 additions & 5 deletions integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,19 @@ Charging AI agents for your API takes three steps, and one decision.

Either way the money path is identical and non-custodial: the agent pays
on-chain **from its own wallet to yours** through the settlement contract, and
what your side checks is a signed receipt. Your server never holds funds.
what your side checks is a signed receipt. Signature verification is local and
stateless; quota consumption is stateful and needs a counter store. Your server
never holds funds.

<Warning>
The default `MemoryStore` is correct for one process only. If you run PM2,
Node cluster, multiple containers, or more than one pod, pass a shared atomic
store such as `redisStore` to `aifpGate`; otherwise each worker can spend the
same prepaid batch independently. Keep the default `onStoreError: "closed"`
behavior so a counter-store outage returns `503` instead of granting an
uncounted request. The receipt's Ed25519/JWKS signature check does not replace
this stateful metering step.
</Warning>

<Note>
There is no sandbox yet: the first payment is real money on Polygon mainnet.
Expand All @@ -60,7 +72,10 @@ The package has two halves with different credentials, on purpose:

What the gate does per request: no receipt → answer `HTTP 402` with
machine-readable payment instructions; valid receipt → verify the Ed25519
signature locally, meter the prepaid quota down, let the request through.
signature locally, atomically meter the prepaid quota down in the configured
store, and let the request through. Signature verification needs only the
public JWKS; quota accounting still needs shared state when the gate runs in
more than one process.
There is no call to AiFinPay on the hot path — our availability never becomes
your latency.

Expand Down Expand Up @@ -94,7 +109,7 @@ await merchant.ensureResources([
]);

const registry = new ResourceRegistry({ merchant });
await registry.start(); // fail fast rather than serve with an empty registry
await registry.start(); // load the initial snapshot before serving

const app = express();
app.use("/api", aifpGate({ merchantId: merchant.merchantId, registry }));
Expand Down Expand Up @@ -167,13 +182,16 @@ HTTP 402
"how_to_pay": [
"POST https://api.aifinpay.io/v1/quote {…}",
"settle the quoted batch on-chain from your own wallet",
"POST https://api.aifinpay.io/v1/pay {quote_id, chain, asset, tx_ref} -> quota receipt",
"POST https://api.aifinpay.io/v1/pay {quote_id, chain, asset, tx_ref, payment_authorization when the quote requires it} -> quota receipt",
"retry this request with header: AIFP-Receipt: <receipt JWT>"
]
}
```

Agents on the AiFinPay SDK ([Node](/pay/node), [Python](/pay/python),
Keep the complete quote response. When it includes `payment_authorization`, the
client must sign the wallet-bound authorization described by that object and
send it with `/v1/pay`; do not invent or omit that field in a hand-written
client. Agents on the AiFinPay SDK ([Node](/pay/node), [Python](/pay/python),
[MCP](/pay/mcp)) resolve that whole conversation from one `agent.pay(url)`
call.

Expand Down
26 changes: 23 additions & 3 deletions introduction.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: "AiFinPay — Payment Infrastructure for AI Agents"
description: "Stripe for autonomous AI agents. One line of code settles a real on-chain payment over HTTP, then returns the gated response. Non-custodial, multichain, token-free."
description: "Stripe for autonomous AI agents. One line of code settles a real on-chain payment over HTTP on currently enabled routes, then returns the gated response. Non-custodial, multichain, token-free."
---

**AiFinPay is payment infrastructure for autonomous AI agents.** One line of code — `agent.pay(url)` — and your agent settles a real on-chain payment — live across 12 networks — then receives the gated response.
**AiFinPay is payment infrastructure for autonomous AI agents.** One line of code — `agent.pay(url)` — and your agent settles a real on-chain payment on the route advertised by the challenge, then receives the gated response. Current payment-live routes and their evidence are tracked in the [network readiness reference](/reference/networks); the SDK's live settlement path is Polygon while per-chain routing is rolling out.

It is the same idea Stripe brought to web checkouts, applied to machine-to-machine commerce: a drop-in rail an agent uses to pay for any API call, and a drop-in way for a provider to charge for one.

Expand All @@ -12,7 +12,7 @@ It is the same idea Stripe brought to web checkouts, applied to machine-to-machi
Agents hold their own keys and sign their own transactions. AiFinPay never takes custody of funds.
</Card>
<Card title="Multichain" icon="link">
Contracts live on 12 mainnet networks — 7 with the full smart-contract stack. SDK settlement is live on Polygon today; per-chain routing is rolling out.
Network inventory and deployment status are tracked by readiness state. SDK settlement is live on Polygon today; per-chain routing is rolling out only as each route passes verification and paid end-to-end checks.
</Card>
<Card title="Token-free" icon="circle-check">
No protocol token to buy, hold, or speculate on. Agents pay in MATIC/POL or SOL — the assets they already hold.
Expand Down Expand Up @@ -89,6 +89,26 @@ both, independently.
</Card>
</CardGroup>

## The dashboard

Everything above can be driven from code alone, but the running product has a
control panel: **[dash.aifinpay.io](https://dash.aifinpay.io)**.

It is where you register a site, price its endpoints, watch which agents paid
for what, set a payout wallet, and configure webhooks. Whatever you declare in
code with `ensureResources()` shows up there, and whatever you draw there is
picked up by the gate within about a minute — the two surfaces edit the same
registry, so you can start in either one.

<CardGroup cols={2}>
<Card title="Set up your site" icon="gauge" href="/charge/dashboard">
Endpoints, pricing tiers, payout wallet, and the paywall builder.
</Card>
<Card title="Connect your gateway" icon="server" href="/charge/gateway">
Point your domain at AiFinPay, or verify receipts in your own backend.
</Card>
</CardGroup>

## SDKs and tooling

| Package | Install | Use it for |
Expand Down
17 changes: 9 additions & 8 deletions quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ hold to use it.

agent = Agent.new()
print("Fund this address with a few cents of MATIC:", agent.address)
print("Save this secret:", agent.secret_b58)
# If reusing this identity, store agent.secret_b58 in a local secret
# manager or keypair file; never print it into shared logs.
```

```ts Node
Expand Down Expand Up @@ -105,9 +106,10 @@ You saw one function call. Under the hood:
chain, asset, `payTo` address, amount, and a one-time `nonce`.
</Step>
<Step title="Settle on-chain">
The SDK submitted one transaction on Polygon mainnet — a gross-inclusive
AIFP-1 split (merchant 99% / AiFinPay 1% / creator 0%) in one verified tx. No
custodian touches the funds at any point.
The SDK submits one transaction using the settlement terms in the
challenge. It verifies the quoted amount and route economics before
signing; no custodian touches the funds at any point. For canonical AIFP-1
economics, see [Pricing, units and limits](/reference/pricing-and-limits).
</Step>
<Step title="Retry with proof">
The SDK retried your request with the payment proof headers, the
Expand All @@ -131,12 +133,11 @@ You saw one function call. Under the hood:
<Card title="Node / TypeScript SDK" icon="node-js" href="/pay/node">
Full `@aifinpay/agent` API for TypeScript and Node runtimes.
</Card>
<Card title="MCP server" icon="plug" href="/mcp">
<Card title="MCP server" icon="plug" href="/pay/mcp">
Zero-code: drop `@aifinpay/mcp` into Claude Desktop, Cursor, or
Windsurf and let the model pay for x402-gated APIs itself.
</Card>
<Card title="Framework adapters" icon="diagram-project" href="/integrations">
Paste-and-run examples for LangChain, CrewAI, OpenAI Agents, AutoGPT,
and Flowise.
<Card title="Self-hosted integration" icon="diagram-project" href="/integration">
Put a payment gate in front of your own API and verify receipts locally.
</Card>
</CardGroup>
59 changes: 59 additions & 0 deletions scripts/check-internal-links.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node

import fs from "node:fs";
import path from "node:path";
import process from "node:process";

const root = process.cwd();
const configPath = path.join(root, "docs.json");
const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
const pages = new Set();
const files = [];

function walk(dir) {
for (const name of fs.readdirSync(dir)) {
if (name === ".git" || name === "node_modules") continue;
const file = path.join(dir, name);
const stat = fs.statSync(file);
if (stat.isDirectory()) walk(file);
else if (/\.(md|mdx)$/.test(name)) files.push(file);
}
}

function configuredPages() {
for (const tab of config.navigation?.tabs ?? []) {
for (const group of tab.groups ?? []) {
for (const page of group.pages ?? []) pages.add(`/${page}`);
}
}
}

walk(root);
configuredPages();
const missingFiles = [...pages].filter(
(page) => !fs.existsSync(path.join(root, `${page.slice(1)}.mdx`)),
);
const missingLinks = [];
const missingAssets = [config.favicon, config.logo?.light, config.logo?.dark]
.filter((asset) => typeof asset === "string" && asset.startsWith("/"))
.filter((asset) => !fs.existsSync(path.join(root, asset.slice(1))));
const linkPattern = /(?:\]\(|href=["'])(\/[A-Za-z0-9_./-]+)/g;

for (const file of files) {
const source = fs.readFileSync(file, "utf8");
for (const match of source.matchAll(linkPattern)) {
const target = match[1].replace(/[?#].*$/, "");
if (!pages.has(target) && !fs.existsSync(path.join(root, `${target.slice(1)}.mdx`))) {
missingLinks.push(`${path.relative(root, file)} -> ${target}`);
}
}
}

if (missingFiles.length || missingLinks.length || missingAssets.length) {
if (missingFiles.length) console.error("Missing configured pages:\n" + missingFiles.join("\n"));
if (missingLinks.length) console.error("Missing internal links:\n" + missingLinks.join("\n"));
if (missingAssets.length) console.error("Missing configured assets:\n" + missingAssets.join("\n"));
process.exitCode = 1;
} else {
console.log(`OK: ${pages.size} configured pages and no missing internal links`);
}
Loading