@tech0/sdk
v0.1.3
Published
Opinionated Tech0 HTTP client and browser analytics helpers for tenant integrations
Downloads
59
Readme
Tech0-sdk Integration
TypeScript client for Tech0 tenant API routes at https://api.tech0.se.
Use it from server code with one tenant site_api_key.
Install
npm install @tech0/sdkEnvironment
TECH0_API_BASE=https://api.tech0.se
TECH0_API_KEY=<site_api_key>Create Client
import { createTech0Client } from "@tech0/sdk";
export const tech0 = createTech0Client({
baseUrl: process.env.TECH0_API_BASE ?? "https://api.tech0.se",
apiKey: process.env.TECH0_API_KEY!,
timeoutMs: 30_000,
retries: 3,
});The SDK sends x-api-key: <site_api_key> by default. You can also use
authHeader: "authorization" (Authorization: Bearer <site_api_key>),
"x-site-api-key", or "x-tech0-key" to match your integration.
Tenant Ping Route
For portal "Test SDK connection" on tenant websites, expose a same-origin route that calls Tech0 with the configured server-side SDK client.
// app/api/tech0-sdk/ping/route.ts
import { NextResponse } from "next/server";
import { createTech0Client } from "@tech0/sdk";
const tech0 = createTech0Client({
baseUrl: process.env.TECH0_API_BASE ?? "https://api.tech0.se",
apiKey: process.env.TECH0_API_KEY,
});
export async function GET() {
const result = await tech0.ping();
return NextResponse.json({ success: true, ok: true, tech0: result });
}client.ping() and client.tenant.ping() both call
GET /api/tenant/ping and expect:
{ success: true, ok: true, tenant?: { id: string } }Examples
const ping = await tech0.ping();
const genericPing = await tech0.request("/api/tenant/ping");const feed = await tech0.pim.getFeed({
include: ["categories", "selections"],
refresh: true,
});const imageUrl = await tech0.pim.resolveImageUrl("product-123");PIM checkout discounts (server-only)
Compute subtotalAmount / shippingAmount on your server. Optionally call
previewDiscount for UI. Before creating a payment, call redeemDiscount with
the same amounts, currency, code, and a stable checkoutReference, then
charge using discount.finalAmount and discount.currency from the redeem
response when valid is true.
These routes allow browser CORS (Access-Control-Allow-Origin: *), but do
not call them from the client: keep the site API key on the server. Both
endpoints share a PIM rate limit (30 req/min per key; 429 with Retry-After).
const preview = await tech0.pim.previewDiscount({
code: "SUMMER10",
subtotalAmount: 199.5,
shippingAmount: 49,
currency: "SEK",
});
const redeemed = await tech0.pim.redeemDiscount({
code: "SUMMER10",
subtotalAmount: 199.5,
shippingAmount: 49,
currency: "SEK",
checkoutReference: "checkout_abc123",
});Invalid or ineligible codes often return HTTP 200 with
{ success: true, valid: false, reason } rather than throwing; validation
errors use HTTP 400 with { success: false, error, code? }.
resolveImageUrl performs an authenticated GET /api/pim/image/{productId}.
If Tech0 redirects to an asset, it returns the final redirected URL. If Tech0
returns the placeholder SVG with 200, it returns the requested Tech0 image URL.
const articles = await tech0.articles.listArticles({
status: "published",
limit: 100,
offset: 0,
});
const article = await tech0.articles.getArticle("my-slug");Article detail defaults to status=published. A missing article returns a
structured Tech0Error with status: 404 and code: "NOT_FOUND" when the
server sends that code.
await tech0.success.track({
event_type: "purchase",
event_name: "Checkout completed",
path: "/checkout/success",
metadata: { orderId: "ord_123" },
count: 1,
});Analytics Proxy
Browser sendBeacon cannot attach cross-origin API-key headers, so browser
analytics should usually post to a tenant same-origin proxy route. That route
forwards to Tech0 with x-api-key and should forward x-forwarded-for or
x-real-ip.
// app/api/tech0-analytics/track/route.ts
import { NextRequest, NextResponse } from "next/server";
import { createTech0Client } from "@tech0/sdk";
const tech0 = createTech0Client({
baseUrl: process.env.TECH0_API_BASE ?? "https://api.tech0.se",
apiKey: process.env.TECH0_API_KEY!,
});
export async function POST(req: NextRequest) {
const payload = await req.json();
const forwardedFor = req.headers.get("x-forwarded-for");
const realIp = req.headers.get("x-real-ip");
const result = await tech0.request("/api/tech0-analytics/track", {
method: "POST",
body: payload,
headers: {
...(forwardedFor ? { "x-forwarded-for": forwardedFor } : {}),
...(realIp ? { "x-real-ip": realIp } : {}),
},
});
return NextResponse.json({ success: true, tech0: result });
}Server-side analytics can call Tech0 directly:
await tech0.analytics.track({
event_type: "page_view",
session_id: "session_123",
duration_seconds: 12,
});Generic Request
Use client.request(path, options?) for any supported /api/... tenant route.
The path must start with /api/.
const result = await tech0.request("/api/tenant/ping");Plain object bodies are JSON encoded, JSON responses are parsed, and non-2xx
responses throw Tech0Error:
try {
await tech0.request("/api/articles/missing");
} catch (error) {
// Tech0Error: { status, code, message, requestId, retryAfter, responseBody }
}Retries are applied only to safe or explicitly idempotent calls for 429 and
5xx responses. POST is never retried unless the request opts in with
retry: true or idempotent: true.
Webhooks
verifyWebhook is intentionally not implemented yet. It throws until Tech0
publishes signing headers and canonical body rules.
