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.

Base URL
https://novochrono.com/api/v1
This page is the guide; the reference is interactive. The full parameter list, request/response schemas, and a try-it-live console for every endpoint live in the API Reference— it's generated from the OpenAPI spec, so it's always in sync with the live API. Use this guide for auth, errors, and webhooks; use the reference for endpoint detail.

Authentication

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
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.

1 · Create a link
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" }'
Response — 201 Created
{
  "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:

2 · Configure advanced behaviour
# 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.

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.

Example error (HTTP 402)
{
  "error": "Your Free plan is limited to 25 links. Upgrade to create more.",
  "code": "limit_reached"
}
StatusCodeMeaning
400Invalid input. Validation details are included in details.
401Missing or invalid API key.
402limit_reachedPlan quota reached (links or domains). Upgrade to continue.
404The resource doesn't exist or belongs to another workspace.
409Conflict — the slug is already taken, or the domain isn't verified yet.
422unsafe_urlThe destination was flagged by Google Safe Browsing.
429rate_limitedToo 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.

MethodEndpointDescription
POST/api/v1/linksCreate a link — with any setting (tags, expiry, routing rules, A/B variants, social card, deep link)
POST/api/v1/links/bulkCreate up to 100 links in one request
GET/api/v1/linksList links — filter with ?limit, ?tag, ?search
GET/api/v1/links/exportExport 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}/statsPer-link analytics — timeseries + top countries/devices/browsers/referrers
GET/api/v1/links/{id}/qrQR code for the short URL (SVG, or ?format=png)
DELETE/api/v1/links/{id}Delete a link
GET/api/v1/analyticsWorkspace summary — total links, total clicks, top links
GET/api/v1/domainsList branded domains (for domainId)

Open the interactive reference ↗

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.)

EventWhenData
link.createdA link is created (dashboard or API)id, slug, destinationUrl, shortUrl
link.disabledA link is disabledid, slug
Example payload
{
  "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:

Node.js
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);
}