Skip to content
Open
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
4 changes: 3 additions & 1 deletion examples/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here
# OPENAI_API_KEY=sk-your-openai-key-here

# Test Server URL (for axios/fetch/node-http examples)
DUMMY_SERVER_URL=https://x402-demo.sapiom.ai
# `npm start` / `npm run free` starts the local demo server automatically.
# Override this only when you want to point the examples at another compatible server.
DUMMY_SERVER_URL=http://localhost:3101
49 changes: 31 additions & 18 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ SAPIOM_API_KEY=your-key-from-step-1
SAPIOM_API_URL=https://api.sapiom.ai

# For axios/fetch/node-http examples:
DUMMY_SERVER_URL=https://x402-demo.sapiom.ai
DUMMY_SERVER_URL=http://localhost:3101

# For langchain examples only:
ANTHROPIC_API_KEY=sk-ant-your-key
```

> **Note:** The `DUMMY_SERVER_URL` points to a public demo server that simulates paid API endpoints. The default URL in `.env.example` is ready to use.
> **Note:** `npm start`, `npm run free`, and `npm run full` for the axios, fetch, and node-http examples automatically start a local demo server at `http://localhost:3101`. Override `DUMMY_SERVER_URL` only when you want to point the examples at another compatible server.

### 3. Run an example

Expand All @@ -56,6 +56,7 @@ npm run full # All endpoints (requires Sapiom balance for payments)
```

**Start with `npm start`** - this uses free endpoints and doesn't require a balance. You can test:

- Basic SDK integration
- Authorization tracking
- Usage rules
Expand Down Expand Up @@ -90,11 +91,11 @@ On the second request, you should see an `AuthorizationDeniedError` - this means

## Available Examples

| Example | Package | Best for |
|---------|---------|----------|
| `axios/` | `@sapiom/axios` | Existing Axios codebases |
| `fetch/` | `@sapiom/fetch` | Native fetch API users |
| `node-http/` | `@sapiom/node-http` | Raw Node.js HTTP |
| Example | Package | Best for |
| -------------------- | --------------------------- | ----------------------------------- |
| `axios/` | `@sapiom/axios` | Existing Axios codebases |
| `fetch/` | `@sapiom/fetch` | Native fetch API users |
| `node-http/` | `@sapiom/node-http` | Raw Node.js HTTP |
| `langchain-classic/` | `@sapiom/langchain-classic` | LangChain v0.3.x with tool wrappers |

> **Note:** The `langchain/` example is for LangChain v1.x. Use `langchain-classic/` if you're on LangChain v0.3.x.
Expand All @@ -103,7 +104,9 @@ Start with `axios/` or `fetch/` - they're the simplest to understand.

## About the Demo Server

The examples connect to a demo server that simulates real-world APIs with payment requirements. This server implements the [x402 payment protocol](https://www.x402.org/) - an HTTP standard where APIs can require micropayments.
The axios, fetch, and node-http examples connect to a demo server that simulates real-world API endpoints. `npm start`, `npm run free`, and `npm run full` start the local demo server automatically, so the examples do not depend on the public `x402-demo.sapiom.ai` host being reachable.

The optional paid flow (`npm run full`) exercises the same endpoint shapes locally. To test against a live compatible x402 server instead, set `DUMMY_SERVER_URL` before running the example. x402 is an HTTP standard where APIs can require micropayments.

**How it works:**

Expand All @@ -115,6 +118,7 @@ The examples connect to a demo server that simulates real-world APIs with paymen
You don't need to understand the x402 protocol - the Sapiom SDK handles it transparently. Your code just makes normal HTTP requests.

The demo server simulates a marketing platform with:

- **CRM endpoints** - Customer data (free, but requires authorization)
- **SMS endpoints** - Send messages (paid per message)
- **Analytics endpoints** - Campaign metrics (paid + authorization)
Expand All @@ -125,18 +129,18 @@ The demo server (`DUMMY_SERVER_URL`) provides these endpoints:

### Free endpoints (no balance required)

| Endpoint | Auth | Payment | Description |
|----------|------|---------|-------------|
| `GET /api/public/time` | No | No | Current server time |
| `GET /api/public/status` | No | No | Server health check |
| `GET /api/crm/customers` | Yes | No | Fetch customer list |
| Endpoint | Auth | Payment | Description |
| ------------------------ | ---- | ------- | ------------------- |
| `GET /api/public/time` | No | No | Current server time |
| `GET /api/public/status` | No | No | Server health check |
| `GET /api/crm/customers` | Yes | No | Fetch customer list |

### Paid endpoints (require balance)

| Endpoint | Auth | Payment | Description |
|----------|------|---------|-------------|
| `POST /api/sms` | No | $0.0075 | Send SMS message |
| `POST /api/campaigns/analytics` | Yes | $0.05 | Get campaign analytics |
| Endpoint | Auth | Payment | Description |
| ------------------------------- | ---- | ------- | ---------------------- |
| `POST /api/sms` | No | $0.0075 | Send SMS message |
| `POST /api/campaigns/analytics` | Yes | $0.05 | Get campaign analytics |

The Sapiom SDK handles all authorization and payment automatically - your code just makes normal HTTP requests.

Expand All @@ -145,15 +149,24 @@ The Sapiom SDK handles all authorization and payment automatically - your code j
## Troubleshooting

**"SAPIOM_API_KEY environment variable is required"**

- Make sure you copied `.env.example` to `.env` and filled in your API key

**Connection refused / timeout**
- Check that `DUMMY_SERVER_URL` is correct and the test server is running

- For `npm start`, `npm run free`, and `npm run full`, check that port `3101` is available; those scripts start the local demo server automatically.
- If you override `DUMMY_SERVER_URL`, check that the server is reachable and implements the demo endpoints listed above.

**403 from x402-demo.sapiom.ai**

- The public demo host can be blocked by Cloudflare or unavailable from some environments. Use the default local demo server for free examples, or point `DUMMY_SERVER_URL` at another compatible demo server.

**AuthorizationDeniedError on first request**

- Check your Rules in the dashboard - you may have a restrictive policy

**Nothing showing in dashboard**

- Verify your `SAPIOM_API_KEY` is correct
- Check the console output for errors

Expand Down
2 changes: 2 additions & 0 deletions examples/axios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ npm run free # Same as above
npm run full # All endpoints including paid (requires balance)
```

`npm start`, `npm run free`, and `npm run full` start the local demo server automatically and set `DUMMY_SERVER_URL=http://localhost:3101` for the run. Override `DUMMY_SERVER_URL` only when you want to test against another compatible server.

## What This Shows

- Wrap your existing Axios instance with `withSapiom()`
Expand Down
6 changes: 3 additions & 3 deletions examples/axios/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"description": "Sapiom SDK example using Axios",
"scripts": {
"start": "npx tsx index-free.ts",
"free": "npx tsx index-free.ts",
"full": "npx tsx index.ts"
"start": "node ../run-local-demo.mjs index-free.ts",
"free": "node ../run-local-demo.mjs index-free.ts",
"full": "node ../run-local-demo.mjs index.ts"
},
"dependencies": {
"@sapiom/axios": "latest",
Expand Down
2 changes: 2 additions & 0 deletions examples/fetch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ npm run free # Same as above
npm run full # All endpoints including paid (requires balance)
```

`npm start`, `npm run free`, and `npm run full` start the local demo server automatically and set `DUMMY_SERVER_URL=http://localhost:3101` for the run. Override `DUMMY_SERVER_URL` only when you want to test against another compatible server.

## What This Shows

- Create a Sapiom-wrapped fetch function with `createFetch()`
Expand Down
6 changes: 3 additions & 3 deletions examples/fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"description": "Sapiom SDK example using native Fetch API",
"scripts": {
"start": "npx tsx index-free.ts",
"free": "npx tsx index-free.ts",
"full": "npx tsx index.ts"
"start": "node ../run-local-demo.mjs index-free.ts",
"free": "node ../run-local-demo.mjs index-free.ts",
"full": "node ../run-local-demo.mjs index.ts"
},
"dependencies": {
"@sapiom/fetch": "latest",
Expand Down
159 changes: 159 additions & 0 deletions examples/local-demo-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env node
import http from "node:http";

const port = Number.parseInt(process.env.DUMMY_SERVER_PORT ?? "3101", 10);
const startedAt = Date.now();

const customers = [
{
id: "cus_demo_enterprise_1",
name: "Acme Corp",
email: "ops@acme.example",
phone: "+15550100001",
segment: "enterprise",
revenue: 250000,
},
{
id: "cus_demo_enterprise_2",
name: "Globex",
email: "team@globex.example",
phone: "+15550100002",
segment: "enterprise",
revenue: 180000,
},
{
id: "cus_demo_enterprise_3",
name: "Initech",
email: "hello@initech.example",
phone: "+15550100003",
segment: "enterprise",
revenue: 125000,
},
];

function writeJson(res, status, body) {
res.writeHead(status, {
"content-type": "application/json; charset=utf-8",
"access-control-allow-origin": "*",
});
res.end(JSON.stringify(body));
}

function readJson(req) {
return new Promise((resolve, reject) => {
let body = "";
req.setEncoding("utf8");
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
if (!body) {
resolve({});
return;
}
try {
resolve(JSON.parse(body));
} catch (error) {
reject(error);
}
});
req.on("error", reject);
});
}

const server = http.createServer(async (req, res) => {
const url = new URL(
req.url ?? "/",
`http://${req.headers.host ?? `localhost:${port}`}`,
);

if (req.method === "OPTIONS") {
res.writeHead(204, {
"access-control-allow-origin": "*",
"access-control-allow-methods": "GET,POST,OPTIONS",
"access-control-allow-headers":
"content-type,authorization,x-sapiom-transaction-id",
});
res.end();
return;
}

if (req.method === "GET" && url.pathname === "/api/public/time") {
writeJson(res, 200, {
time: new Date().toISOString(),
timezone: "UTC",
});
return;
}

if (req.method === "GET" && url.pathname === "/api/public/status") {
writeJson(res, 200, {
status: "ok",
version: "local-demo",
uptime: (Date.now() - startedAt) / 1000,
});
return;
}

if (req.method === "GET" && url.pathname === "/api/crm/customers") {
const limit = Number.parseInt(
url.searchParams.get("limit") ?? String(customers.length),
10,
);
const segment = url.searchParams.get("segment");
const filtered = segment
? customers.filter((customer) => customer.segment === segment)
: customers;
writeJson(res, 200, {
customers: filtered.slice(
0,
Number.isFinite(limit) ? limit : filtered.length,
),
total: filtered.length,
});
return;
}

if (req.method === "POST" && url.pathname === "/api/sms") {
const body = await readJson(req);
writeJson(res, 200, {
messageId: `sms_demo_${Date.now()}`,
status: "queued",
price: 0.0075,
to: body.phone,
});
return;
}

if (req.method === "POST" && url.pathname === "/api/campaigns/analytics") {
const body = await readJson(req);
const sent = 3;
const opened = 2;
writeJson(res, 200, {
campaignId: body.campaignId ?? "demo-campaign",
name: "Local Demo Campaign",
status: "completed",
metrics: {
sent,
delivered: sent,
opened,
clicked: 1,
conversions: 1,
openRate: Math.round((opened / sent) * 100),
},
});
return;
}

writeJson(res, 404, { error: "not_found", path: url.pathname });
});

server.listen(port, "127.0.0.1", () => {
console.log(`Local Sapiom demo server listening at http://localhost:${port}`);
});

for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, () => {
server.close(() => process.exit(0));
});
}
2 changes: 2 additions & 0 deletions examples/node-http/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ npm run free # Same as above
npm run full # All endpoints including paid (requires balance)
```

`npm start`, `npm run free`, and `npm run full` start the local demo server automatically and set `DUMMY_SERVER_URL=http://localhost:3101` for the run. Override `DUMMY_SERVER_URL` only when you want to test against another compatible server.

## What This Shows

- Create a Sapiom HTTP client with `createClient()`
Expand Down
6 changes: 3 additions & 3 deletions examples/node-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"private": true,
"description": "Sapiom SDK example using native Node.js HTTP",
"scripts": {
"start": "npx tsx index-free.ts",
"free": "npx tsx index-free.ts",
"full": "npx tsx index.ts"
"start": "node ../run-local-demo.mjs index-free.ts",
"free": "node ../run-local-demo.mjs index-free.ts",
"full": "node ../run-local-demo.mjs index.ts"
},
"dependencies": {
"@sapiom/node-http": "latest",
Expand Down
Loading
Loading