Everything you can do in the dashboard, you can do over HTTP. Create codes, change where they point, read scans, manage your team — straight from your own software. One key, plain JSON, honest limits.
The base path is https://cortalim.com/api. Every request and response is 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, so you never have to filter by hand. Listing and analytics endpoints return only what your role is allowed to read — nothing leaks in from another account.
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. Repoint it whenever you like — the printed code and the short URL never change, only the destination does.
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: a code is never bricked out from under you, and nothing is gone for good until you explicitly ask for a hard delete.
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 send to print. Because the code is dynamic, that one image keeps working for good — change the destination as often as you want and every printed copy still lands in the right place.
Rules decide where each scan lands — by device, country, date, scan count, or an even split. Set one rules object on create or update. Every type falls back to base_url when nothing matches, so a scan always has somewhere to go.
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. So a support code can reach your form already knowing which city it was scanned in.
The access object decides who gets through, and for how long. Combine any of these:
Bot and datacenter filtering drop crawlers, link-preview fetchers and datacenter-IP traffic, so your scan count reflects real people, not noise. Every gate here runs at redirect time, so turning one on never changes the printed code.
Account admins (company_admin and system_admin) can read the team roster and the account's billing state — plan, price and code usage.
Platform staff (system_admin only) get two reads across every account:
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.