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
49 changes: 49 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Agent guide for developers.nextcommerce.com

This file is the navigation and evidence contract for an agent reading the Next Commerce developer documentation. It is not a setup script. Agent setup instructions will live at /agent-setup/prompt.md (tracked in developer-docs#45).

## What this site is

https://developers.nextcommerce.com documents the Next Commerce platform for developers: the Admin API (REST), webhooks, storefront themes and the storefront GraphQL API, campaigns and the Campaign Cart SDK, apps and OAuth, AI agent skills, and testing.

The sibling site, https://docs.nextcommerce.com, is the merchant and operator documentation: dashboard guides, store configuration, payments operations, and the platform changelog. Questions about how to operate a store belong there; questions about how to integrate with it belong here.

## Where to start

- https://developers.nextcommerce.com/llms.txt is the page index for this site, with a one-line description per page and absolute URLs.
- https://developers.nextcommerce.com/llms-full.txt is the full corpus in one file. It is large (about 1.5 MB); fetch it only when you need broad coverage rather than a specific page.
- Raw OpenAPI specs, which are the authority for operations, parameters, and fields:
- https://developers.nextcommerce.com/api/admin/2024-04-01.yaml (stable)
- https://developers.nextcommerce.com/api/admin/unstable.yaml
- https://developers.nextcommerce.com/api/admin/2023-02-10.yaml (deprecated)
- https://developers.nextcommerce.com/api/campaigns/v1.yaml
- https://developers.nextcommerce.com/docs/webhooks lists every webhook event and the payload structure.
- https://developers.nextcommerce.com/docs/testing covers test cards, test orders, and sandbox behavior.
- https://docs.nextcommerce.com/llms.txt is the equivalent index for the merchant site.

## Evidence rules

- Cite the page URL for every claim you make from this documentation.
- For facts about an operation, parameter, or field, prefer the versioned spec over prose pages. The prose explains; the spec is the contract.
- The changelog at https://docs.nextcommerce.com/changelog is the record of what changed and when. Do not infer release history from page contents.
- NEXT Payments processing rates are not published. If asked, say so rather than estimating.
- Do not "correct" identifiers containing 29next. See Legacy identifiers below.
- If the documentation does not answer a question, say that it does not. Do not fill gaps from general ecommerce knowledge and present the result as Next Commerce behavior.

## Versions

The Admin API is versioned by date. The version is selected per request with the `X-29next-API-Version` header.

- `2024-04-01`: stable, recommended for all new integrations.
- `2023-02-10`: deprecated; documented for existing integrations only.
- `unstable`: in-progress changes; may change without notice.

Each version has its own spec file (listed above) and its own reference section on the site. When a question names a version, answer from that version's spec.

## Legacy identifiers

Next Commerce was formerly 29 Next. Hostnames like `{store}.29next.store`, `accounts.29next.com`, and headers like `X-29next-API-Version` and `X-29Next-Signature` are current, valid technical identifiers and must be used exactly as written.

## Corrections

Outside pull requests to this repository are not accepted. The support route for reporting documentation errors is being confirmed and will be linked here.
21 changes: 3 additions & 18 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,3 @@

## Skill routing

When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.

Key routing rules:
- Product ideas/brainstorming → invoke /office-hours
- Strategy/scope → invoke /plan-ceo-review
- Architecture → invoke /plan-eng-review
- Design system/plan review → invoke /design-consultation or /plan-design-review
- Full review pipeline → invoke /autoplan
- Bugs/errors → invoke /investigate
- QA/testing site behavior → invoke /qa or /qa-only
- Code review/diff check → invoke /review
- Visual polish → invoke /design-review
- Ship/deploy/PR → invoke /ship or /land-and-deploy
- Save progress → invoke /context-save
- Resume context → invoke /context-restore
The public contract for agents reading this repository or the site it publishes is AGENTS.md.
See ./AGENTS.md for navigation, evidence rules, API versions, and legacy identifiers.
Internal tool routing for the maintainers' own sessions is not kept in this repository.
112 changes: 110 additions & 2 deletions app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,116 @@
import { source } from '@/lib/source';
import { llms } from 'fumadocs-core/source';

export const revalidate = false;

const SITE = 'https://developers.nextcommerce.com';
const MERCHANT_SITE = 'https://docs.nextcommerce.com';

// Groups named here come first, in this order; any other top-level folder
// follows alphabetically. 'index' is the /docs root page itself.
const PREFERRED_GROUP_ORDER = [
'index',
'admin-api',
'webhooks',
'storefront',
'campaigns',
'apps',
'skills',
'testing',
];

const GROUP_TITLES: Record<string, string> = {
index: 'Overview',
'admin-api': 'Admin API',
webhooks: 'Webhooks',
storefront: 'Storefront',
campaigns: 'Campaigns',
apps: 'Apps',
skills: 'Skills',
testing: 'Testing',
};

function groupTitle(key: string): string {
if (GROUP_TITLES[key]) return GROUP_TITLES[key];
return key
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}

function groupKey(url: string): string {
const rest = url.replace(/^\/docs\/?/, '');
const first = rest.split('/')[0];
return first || 'index';
}

// Frontmatter values are interpolated into Markdown link syntax; keep each
// page on exactly one line and neutralise the characters that would break it.
function oneLine(value: string): string {
return value.replace(/\s+/g, ' ').trim();
}

function linkText(value: string): string {
return oneLine(value).replace(/[\[\]]/g, '\\$&');
}

function header(): string {
return [
'# Next Commerce Developer Docs',
'',
'> API, webhook, storefront theme, campaign, and app documentation for Next Commerce, the ecommerce platform for direct-to-consumer brands. Merchant and operator guides live on the sibling site.',
'',
'## Start here',
'',
`- [Developer docs home](${SITE})`,
`- [Merchant docs](${MERCHANT_SITE}): guides for store operators, on the sibling site`,
`- [Merchant docs index](${MERCHANT_SITE}/llms.txt)`,
`- [Platform and API changelog](${MERCHANT_SITE}/changelog): the developer portal has no changelog of its own`,
`- [Full corpus](${SITE}/llms-full.txt): every page in one file (large, about 1.5 MB)`,
`- [Admin API spec, 2024-04-01](${SITE}/api/admin/2024-04-01.yaml): stable, raw OpenAPI`,
`- [Admin API spec, unstable](${SITE}/api/admin/unstable.yaml): raw OpenAPI`,
`- [Admin API spec, 2023-02-10](${SITE}/api/admin/2023-02-10.yaml): deprecated, raw OpenAPI`,
`- [Campaigns API spec, v1](${SITE}/api/campaigns/v1.yaml): raw OpenAPI`,
`- [Webhooks](${SITE}/docs/webhooks)`,
`- [Skills](${SITE}/docs/skills): AI agent skills for the platform`,
`- [Testing](${SITE}/docs/testing)`,
`- [Agent guide (AGENTS.md)](https://github.com/NextCommerceCo/developer-docs/blob/main/AGENTS.md): navigation and evidence rules for agents reading this site`,
'',
'## Legacy identifiers',
'',
'Next Commerce was formerly 29 Next. Hostnames like `{store}.29next.store`, `accounts.29next.com`, and headers like `X-29next-API-Version` and `X-29Next-Signature` are current, valid technical identifiers and must be used exactly as written.',
'',
].join('\n');
}

export function GET() {
return new Response(llms(source).index());
const groups = new Map<string, string[]>();

for (const page of source.getPages()) {
const key = groupKey(page.url);
const title = linkText(page.data.title ?? page.url);
const description = page.data.description ? linkText(page.data.description) : '';
const line = description
Comment thread
next-devin marked this conversation as resolved.
? `- [${title}](${SITE}${page.url}): ${description}`
: `- [${title}](${SITE}${page.url})`;
const list = groups.get(key) ?? [];
list.push(line);
groups.set(key, list);
}

const keys = [...groups.keys()].sort((a, b) => {
const ia = PREFERRED_GROUP_ORDER.indexOf(a);
const ib = PREFERRED_GROUP_ORDER.indexOf(b);
if (ia !== -1 && ib !== -1) return ia - ib;
if (ia !== -1) return -1;
if (ib !== -1) return 1;
return a.localeCompare(b);
});

const body = keys
.map((key) => `## ${groupTitle(key)}\n\n${groups.get(key)!.join('\n')}\n`)
.join('\n');

return new Response(header() + '\n' + body, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
26 changes: 26 additions & 0 deletions app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Link from 'next/link';

export default function NotFound() {
return (
<main className="flex min-h-screen flex-col items-center justify-center bg-fd-background px-6 text-fd-foreground">
<div className="flex w-full max-w-md flex-col gap-6">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold">Page not found</h1>
<p className="text-sm text-fd-muted-foreground">
The page you asked for does not exist or has moved.
</p>
</div>
<nav aria-label="Recovery links">
<ul className="flex flex-col gap-2 text-sm">
<li><Link href="/docs">Browse all docs</Link></li>
<li><Link href="/docs/admin-api">Admin API</Link></li>
<li><Link href="/docs/webhooks">Webhooks</Link></li>
<li><a href="https://docs.nextcommerce.com">Merchant docs</a></li>
<li><a href="https://docs.nextcommerce.com/changelog">Changelog</a></li>
<li><Link href="/llms.txt">Agent index (llms.txt)</Link></li>
</ul>
</nav>
</div>
</main>
);
}
8 changes: 6 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Sparkles,
} from 'lucide-react';
import { siteConfig } from '@/lib/config';
import stats from '@/lib/generated/stats.json';
import { AlgoliaDocSearch, AlgoliaDocSearchMobile } from '@/components/search';
import { HeroFlow } from '@/components/hero-flow';
import { MouseSpotlight } from '@/components/mouse-spotlight';
Expand Down Expand Up @@ -229,11 +230,11 @@ export default function HomePage() {
<div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 pt-2 text-xs text-fd-muted-foreground">
<div className="flex items-center gap-1.5">
<Code2 size={13} className="text-blue-400" aria-hidden="true" />
<span>175+ REST Endpoints</span>
<span>{`${stats.adminApiOperations} REST Endpoints`}</span>
</div>
<div className="flex items-center gap-1.5">
<Zap size={13} className="text-orange-400" aria-hidden="true" />
<span>22 Webhook Events</span>
<span>{`${stats.webhookEvents} Webhook Events`}</span>
</div>
<div className="flex items-center gap-1.5">
<Globe size={13} className="text-green-400" aria-hidden="true" />
Expand Down Expand Up @@ -410,6 +411,9 @@ export default function HomePage() {
© {new Date().getFullYear()} {siteConfig.companyName}
</span>
<div className="flex items-center gap-4">
<Link href="https://docs.nextcommerce.com/changelog" className="text-xs text-fd-muted-foreground hover:text-fd-foreground transition-colors duration-150">
Comment thread
next-devin marked this conversation as resolved.
Changelog
</Link>
<Link href="https://www.nextcommerce.com" className="text-xs text-fd-muted-foreground hover:text-fd-foreground transition-colors duration-150">
nextcommerce.com
</Link>
Expand Down
2 changes: 2 additions & 0 deletions content/docs/admin-api/guides/external-checkout.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ The example below uses a [Test Card Token](/docs/admin-api/guides/testing-guide)
"statement_descriptor": "BRANDNAME" // See details below
},
```

To route the order to a specific gateway or gateway group, include `payment_gateway` or `payment_gateway_group` (an id, never both) inside `payment_details`. See [Gateway Routing](/docs/admin-api/guides/payment-methods/bankcard#gateway-routing) in the Bankcard guide.
#### Statement Descriptor

Merchants have the option to pass a custom `statement_descriptor` on orders so the end customer will more easily recognize the charge on their card bank statement. Using this field will override all subsequent transactions for the bankcard, even across payment gateways.
Expand Down
4 changes: 2 additions & 2 deletions content/docs/admin-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ https://{store}.29next.store/api/admin/
```shell title="Example Request"
curl -X GET "https://{store}.29next.store/api/admin/" \
-H "Authorization: Bearer <api access token>" \
-H "X-29Next-Api-Version: 2024-04-01"
-H "X-29next-API-Version: 2024-04-01"
```

<Callout type="warn" title="Do NOT publish or share Admin API authentication tokens">
Expand All @@ -48,7 +48,7 @@ API versioning allows Next Commerce to continuously evolve the platform while ma

**Specify an API Version**

To specify a version, pass the `X-29Next-Api-Version` header with your desired API version.
To specify a version, pass the `X-29next-API-Version` header with your desired API version.

<Callout type="warn">
It is **highly recommended** to specify your version on your API requests to ensure consistency for your integration.
Expand Down
2 changes: 1 addition & 1 deletion content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Use themes to control the appearance and behavior of your storefront — product

1. Install [Theme Kit](/docs/storefront/themes/theme-kit), the CLI for local theme development
2. Start from [Intro Bootstrap](https://github.com/NextCommerceCo/intro-bootstrap) starter theme
3. Run `theme pull` to sync files locally, make changes, then `theme push` to deploy
3. Run `ntk pull` to sync files locally, make changes, then `ntk push` to deploy

**Resources**
- [Themes overview](/docs/storefront/themes) — theme structure and Theme Kit
Expand Down
2 changes: 2 additions & 0 deletions content/docs/webhooks/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ Returning a `410` response code indicates the target resource is no longer avail

<Callout type="info" title="Handling subscription charges">
The `subscription` object on a `transaction.created` webhook links the charge to its subscription and identifies the billing cycle. See [Identifying Subscription Charges](/docs/admin-api/guides/subscription-management#identifying-subscription-charges).

There is no renewal-specific event. A renewal charge arrives as `transaction.created` with `billing_cycle` of 1 or higher on that `subscription` object; the initial charge has `billing_cycle` 0.
</Callout>

### Webhook Data Structure
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "node scripts/generate-api-docs.mjs && npm run generate-graphql-docs && npm run build:preview && next dev --turbopack",
"build": "node scripts/generate-api-docs.mjs && npm run generate-graphql-docs && npm run build:preview && next build",
"dev": "node scripts/generate-api-docs.mjs && node scripts/generate-stats.mjs && npm run generate-graphql-docs && npm run build:preview && next dev --turbopack",
"build": "node scripts/generate-api-docs.mjs && node scripts/generate-stats.mjs && npm run generate-graphql-docs && npm run build:preview && next build",
"start": "next start",
"generate-api-docs": "node scripts/generate-api-docs.mjs",
"generate-graphql-docs": "node scripts/generate-graphql-docs.mjs",
"generate-stats": "node scripts/generate-stats.mjs",
"build:preview": "node scripts/build-preview.mjs",
"validate-links": "node scripts/validate-links.mjs",
"check-agent-surfaces": "node scripts/check-agent-surfaces.mjs",
"postinstall": "patch-package"
},
"dependencies": {
Expand Down
77 changes: 77 additions & 0 deletions scripts/check-agent-surfaces.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Post-build assertions for the agent entry surfaces.
* Run after `npm run build`; exits 1 on the first failing group.
*/

import { readFileSync, existsSync } from 'fs';
import { join, resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(__dirname, '..');
const OUT = join(ROOT, 'out');

const failures = [];
function check(condition, message) {
if (!condition) failures.push(message);
}

function read(path) {
return existsSync(path) ? readFileSync(path, 'utf8') : null;
}

// llms.txt
const llmsPath = join(OUT, 'llms.txt');
const llms = read(llmsPath);
check(llms !== null, 'out/llms.txt does not exist');
if (llms !== null) {
const firstLine = llms.split('\n')[0];
check(firstLine === '# Next Commerce Developer Docs', `llms.txt first line is ${JSON.stringify(firstLine)}`);
const relative = llms.match(/\]\(\//g) ?? [];
check(relative.length === 0, `llms.txt has ${relative.length} relative markdown link(s)`);
for (const needle of [
'https://docs.nextcommerce.com/llms.txt',
'https://docs.nextcommerce.com/changelog',
'/api/admin/2024-04-01.yaml',
'/docs/webhooks',
'AGENTS.md',
]) {
check(llms.includes(needle), `llms.txt is missing ${JSON.stringify(needle)}`);
}
}

// 404 page
const notFound = read(join(OUT, '404.html'));
check(notFound !== null, 'out/404.html does not exist');
if (notFound !== null) {
for (const needle of ['/llms.txt', 'https://docs.nextcommerce.com']) {
check(notFound.includes(needle), `404.html is missing ${JSON.stringify(needle)}`);
}
}

// stats.json and the home page
const statsPath = join(ROOT, 'lib', 'generated', 'stats.json');
const statsRaw = read(statsPath);
check(statsRaw !== null, 'lib/generated/stats.json does not exist');
if (statsRaw !== null) {
const stats = JSON.parse(statsRaw);
check(stats.adminApiOperations > 0, `stats.adminApiOperations is ${stats.adminApiOperations}`);
check(stats.webhookEvents > 0, `stats.webhookEvents is ${stats.webhookEvents}`);
const index = read(join(OUT, 'index.html'));
Comment thread
next-devin marked this conversation as resolved.
check(index !== null, 'out/index.html does not exist');
if (index !== null) {
for (const needle of [
`<span>${stats.adminApiOperations} REST Endpoints</span>`,
`<span>${stats.webhookEvents} Webhook Events</span>`,
]) {
check(index.includes(needle), `index.html is missing ${JSON.stringify(needle)}`);
}
}
}

if (failures.length > 0) {
console.error('check-agent-surfaces: FAIL');
for (const f of failures) console.error(` - ${f}`);
process.exit(1);
}
console.log('check-agent-surfaces: OK');
Loading