Developer Guide
Everything you need to start using the Novochrono API — then head to the interactive reference to explore and try every endpoint.
Overview
The Novochrono API is a REST API. It accepts JSON request bodies, returns JSON responses, and uses standard HTTP methods and status codes. Every endpoint is scoped to the workspace that owns the API key making the request.
https://novochrono.com/api/v1Authentication
Authenticate every request with an API key in the Authorization header. Create keys in Dashboard → Developers — the full key (format lc_…) is shown once at creation and stored only as a hash, so copy it immediately. Keys can be revoked at any time.
curl https://novochrono.com/api/v1/links \
-H "Authorization: Bearer lc_YOUR_API_KEY"Keep keys server-side. Never embed them in client-side code, mobile apps, or public repositories — anyone with the key can manage your workspace's links.
Quickstart
Create a link, then list your links. Every write goes through the same pipeline as the dashboard, so plan quotas, rate limits, and Safe Browsing all apply.
curl -X POST https://novochrono.com/api/v1/links \
-H "Authorization: Bearer lc_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "destinationUrl": "https://example.com/summer-sale", "slug": "sale" }'{
"id": "0b9155ff-3bb5-4f0e-9f01-2a41d9f0e611",
"shortUrl": "https://novochrono.com/sale",
"link": { "slug": "sale", "destinationUrl": "https://example.com/summer-sale", "clicks": 0, ... }
}The shortUrl is live immediately. The short URL never changes — to repoint a link, PATCH its destinationUrl. Every advanced feature (routing, A/B, social cards, deep links, tags) can be set right in the create call or changed later with the same PATCH fields:
# The same PATCH endpoint drives every advanced feature.
curl -X PATCH https://novochrono.com/api/v1/links/0b9155ff-3bb5-4f0e-9f01-2a41d9f0e611 \
-H "Authorization: Bearer lc_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rules": [ { "kind": "country", "value": "IN", "url": "https://example.com/india" } ],
"variants": [
{ "url": "https://example.com/a", "weight": 50 },
{ "url": "https://example.com/b", "weight": 50 }
],
"tags": ["campaign"]
}'A Rule is { kind, value, url } (kind = country with an ISO code, or device = mobile/desktop/tablet). A Variant is { url, weight } (weight 1–100). The API Reference lists every field with its exact schema.
The link object
Links come back in a consistent shape everywhere. clicks is the live total counted at the edge. flagged is set by the automatic Safe Browsing re-scan; flagged or disabled links stop redirecting immediately. The advanced fields (rules, variants, og*, deepLinkUrl, tags) are null until you set them.
{
"id": "0b9155ff-3bb5-4f0e-9f01-2a41d9f0e611",
"workspaceId": "57e3e388-dd2b-4a10-8431-14c1f8271e8a",
"domainId": null,
"slug": "sale",
"destinationUrl": "https://example.com/summer-sale",
"clicks": 42,
"disabled": false,
"flagged": false,
"flagReason": null,
"forwardQuery": true,
"rules": null,
"variants": null,
"ogTitle": null,
"ogDescription": null,
"ogImage": null,
"deepLinkUrl": null,
"tags": ["campaign"],
"lastScanAt": "2026-07-18T09:30:00.000Z",
"expiresAt": null,
"createdAt": "2026-07-18T09:30:00.000Z"
}Errors
Errors return an appropriate HTTP status and a JSON body with a human-readable error message. Some errors also carry a machine-readable code.
{
"error": "Your Free plan is limited to 25 links. Upgrade to create more.",
"code": "limit_reached"
}| Status | Code | Meaning |
|---|---|---|
400 | — | Invalid input. Validation details are included in details. |
401 | — | Missing or invalid API key. |
402 | limit_reached | Plan quota reached (links or domains). Upgrade to continue. |
404 | — | The resource doesn't exist or belongs to another workspace. |
409 | — | Conflict — the slug is already taken, or the domain isn't verified yet. |
422 | unsafe_url | The destination was flagged by Google Safe Browsing. |
429 | rate_limited | Too many requests. Back off and retry. |
Rate limits
Link creation is limited to 30 requests per 10 seconds per workspace (HTTP 429, rate_limited). Plan quotas also apply: the Free plan includes 25 links and 1 branded domain; Pro raises that to 10,000 links and 25 domains (HTTP 402, limit_reached).
Endpoints
The full API at a glance. For parameters, schemas, example responses, and a live try-it console, open any of these in the interactive API Reference.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/links | Create a link — with any setting (tags, expiry, routing rules, A/B variants, social card, deep link) |
| POST | /api/v1/links/bulk | Create up to 100 links in one request |
| GET | /api/v1/links | List links — filter with ?limit, ?tag, ?search |
| GET | /api/v1/links/export | Export all links as JSON or ?format=csv |
| GET | /api/v1/links/{id} | Retrieve a link (with per-variant clicks) |
| PATCH | /api/v1/links/{id} | Update any setting — destination, kill switch, expiry, query forwarding, geo/device rules, A/B variants, social card, deep link, tags |
| GET | /api/v1/links/{id}/stats | Per-link analytics — timeseries + top countries/devices/browsers/referrers |
| GET | /api/v1/links/{id}/qr | QR code for the short URL (SVG, or ?format=png) |
| DELETE | /api/v1/links/{id} | Delete a link |
| GET | /api/v1/analytics | Workspace summary — total links, total clicks, top links |
| GET | /api/v1/domains | List branded domains (for domainId) |
Webhooks
Register webhook endpoints in Dashboard → Developers. Novochrono POSTs a JSON payload to every registered endpoint when an event occurs. Respond with a 2xx quickly; do heavy work asynchronously. (Webhooks are outbound events, so they aren't part of the REST reference — they're documented here.)
| Event | When | Data |
|---|---|---|
link.created | A link is created (dashboard or API) | id, slug, destinationUrl, shortUrl |
link.disabled | A link is disabled | id, slug |
{
"event": "link.created",
"data": {
"id": "0b9155ff-3bb5-4f0e-9f01-2a41d9f0e611",
"slug": "sale",
"destinationUrl": "https://example.com/summer-sale",
"shortUrl": "https://novochrono.com/sale"
},
"timestamp": "2026-07-18T09:30:00.000Z"
}Verifying signatures
Every delivery includes an X-Novochrono-Event header naming the event, and an X-Novochrono-Signature header containing an HMAC-SHA256 of the raw request body, signed with the endpoint's secret (shown in the dashboard). Verify it before trusting the payload:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySignature(rawBody, signatureHeader, secret) {
const expected =
"sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}