Everything you can do in the dashboard, you can do over HTTP. Create codes, change where they point, read scans, manage your team. One key, plain JSON, honest limits.
The base path is https://cortalim.com/api. Send and receive JSON. Two endpoints are public; the rest need your key.
GET /api/health — service check, no key needed.GET /api/plans — the public plan catalogue, no key needed.Response:
Every authenticated request carries your API key. The header is preferred:
X-API-Key: <your key>
Two fallbacks exist for quick tests and browser links: a key query parameter, or a bearer token. All three resolve to the same key.
The examples on this page use the demo key sk_demo_0000000000000000. It's read-only and shared, so treat anything you see with it as public. Use your own key from the dashboard for real work. A missing or revoked key returns 401 unauthorized.
Each key gets 120 requests per minute. Go over and you get 429 with a retry_after in seconds. Wait it out and keep going — nothing is penalised beyond the pause.
Your key belongs to a user, and that user has a role. The role decides which links you see — you never have to filter by hand. Listing and analytics endpoints already return only what your role is allowed to read.
member — sees the links it created. Scope: own.developer — sees the whole account's links. Scope: account.company_admin — sees the whole account, plus team and billing. Scope: account.system_admin — sees everything across the platform. Scope: all.Asking for a link outside your scope returns 404, not 403 — accounts stay invisible to each other.
GET /api/me tells you who the key belongs to, the plan, your scope, and how many codes you've used against the plan cap.
A link is one short code plus one QR image. Change where it points whenever you like; the printed code follows.
POST /api/links. Only base_url is required. Pass a code to pick your own (3–24 letters and digits) or leave it out for a short one. Optional params, inject, rules and access are described below.
Response — 201 Created:
A custom code that's already taken returns 409 code_taken; a bad length returns 422 code_length; an invalid URL returns 422 invalid_base_url. If you're at your plan's code cap, create returns 402 plan_limit_reached with the plan and its limit.
GET /api/links returns the links your role can see. GET /api/links/{code} returns one.
PATCH /api/links/{code}. Send only the fields you want to change: any of base_url, title, params, inject, rules, access, or active. Point an old printed code somewhere new, or pause it, without touching the rest.
The response is the full updated link, same shape as create.
DELETE /api/links/{code} is a soft deactivate by default — the code stops routing but stays yours to switch back on. This is the no-hostage guarantee at the API level: your codes are never quietly deleted.
GET /api/links/{code}/analytics returns the numbers for one code: total and unique scans, a daily timeseries, breakdowns by device, browser, country and city, and the most recent scans.
GET /api/analytics gives the same kind of totals for the whole account in your scope: total and unique scans, active links, scans today, a timeseries, device and browser breakdowns, your top links, and a recent feed.
GET /api/links/{code}/qr returns the QR image path and the short URL for a code.
The qr_png path renders a PNG you can drop into a design or print run. Because the code is dynamic, the same image keeps working after you change where it points.
Rules decide where a scan lands. Set one rules object on create or update. Every type falls back to base_url when nothing matches.
Alongside routing, params are fixed values carried in the link (a table number, a campaign name), and inject adds live scan context at redirect time — pick from values like city and device. A support code can arrive already knowing where it was scanned.
The access object gates a code. Combine any of these:
Bot and datacenter filtering drop crawlers, link-preview fetchers and datacenter-IP traffic, so your scan count leans much closer to real people. They apply at redirect time, so turning them on never changes the printed code.
Account admins (company_admin and system_admin) can read the team roster and the account's billing state.
Platform staff (system_admin only) get two cross-account reads:
Call an admin endpoint without the role and you get 403 forbidden with a need field naming the capability you're missing.
Errors come back as JSON with an error string and, where it helps, a hint or the specific capability you need. The status code tells you what happened:
401 unauthorized — missing or revoked key.402 plan_limit_reached — you're at your plan's code cap.403 forbidden — your role can't reach this endpoint.404 not_found — no such code, or it's outside your scope.405 method_not_allowed — wrong verb for this path.409 code_taken — that custom code is in use.422 — a field didn't validate (invalid_base_url, code_length, nothing_to_update).429 rate_limited — over 120 requests this minute; retry after the pause.GET /api/health — public service check.GET /api/plans — public plan catalogue.GET /api/me — your account, role, plan and code usage.POST /api/links — create a link.GET /api/links — list links in scope.GET /api/links/{code} — read one link.PATCH /api/links/{code} — update a link.DELETE /api/links/{code} — deactivate (add ?hard=1 to delete).GET /api/links/{code}/analytics — one link's scans.GET /api/links/{code}/qr — QR image path and short URL.GET /api/analytics — account-wide totals in scope.GET /api/account/users — team roster (admin).GET /api/account/billing — plan and usage (admin).GET /api/admin/accounts — all accounts (platform).GET /api/admin/stats — platform totals (platform).Stuck on something, or hit a response that doesn't match this page? Email support@cortalim.com and a real person will read it.