{"components":{"responses":{"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `not_found`."},"RateLimited":{"content":{"application/json":{"example":{"error":"rate_limited","message":"This key has made its links for this hour."},"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Over this hour's limit. Try again after `Retry-After` seconds.","headers":{"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}}}},"Unauthorized":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `missing_key`, `invalid_key`, `revoked_key`."}},"schemas":{"Count":{"properties":{"clicks":{"type":"integer"},"value":{"description":"A domain, a two-letter country code, or phone/tablet/desktop. Empty: unknown or none.","type":"string"}},"required":["value","clicks"],"type":"object"},"CreatedLink":{"allOf":[{"$ref":"#/components/schemas/Link"},{"properties":{"manage_url":{"description":"The link's page with owner controls. Private.","format":"uri","type":"string"}},"required":["manage_url"],"type":"object"}]},"Day":{"properties":{"clicks":{"type":"integer"},"day":{"format":"date","type":"string"},"qr":{"description":"Of `clicks`, how many were QR scans.","type":"integer"}},"required":["day","clicks","qr"],"type":"object"},"Error":{"properties":{"error":{"description":"A stable code to act on.","type":"string"},"message":{"description":"A sentence for a person.","type":"string"}},"required":["error"],"type":"object"},"Key":{"properties":{"created_at":{"format":"date-time","type":"string"},"label":{"type":["string","null"]},"last_used_at":{"description":"To the minute or so.","format":"date-time","type":["string","null"]},"limits":{"properties":{"links_per_hour":{"type":"integer"},"requests_per_hour":{"type":"integer"}},"required":["links_per_hour","requests_per_hour"],"type":"object"},"links_created":{"type":"integer"},"prefix":{"description":"The first 12 characters: the key's public name.","type":"string"},"revoked_at":{"format":"date-time","type":["string","null"]},"status":{"enum":["active","revoked"],"type":"string"}},"required":["prefix","status","created_at","links_created","limits"],"type":"object"},"Link":{"properties":{"clicks":{"description":"All counted visits, QR scans included.","type":"integer"},"code":{"type":"string"},"created_at":{"format":"date-time","type":"string"},"decode_url":{"description":"Its public page.","format":"uri","type":"string"},"last_click_at":{"format":"date-time","type":["string","null"]},"owned":{"description":"Whether your key made it.","type":"boolean"},"qr":{"properties":{"png":{"description":"Add `?size=` (256 to 2048), `?logo=1` for the NOR Bit mark in the middle, and `?dl=1` to download.","format":"uri","type":"string"},"svg":{"description":"Add `?logo=1` for the NOR Bit mark in the middle.","format":"uri","type":"string"}},"required":["svg","png"],"type":"object"},"qr_scans":{"type":"integer"},"short_url":{"format":"uri","type":"string"},"status":{"description":"`flagged`: reported by several people; visitors see a warning page before they continue. `paused` by its owner, `disabled` for abuse: neither redirects.","enum":["active","flagged","paused","disabled"],"type":"string"},"target_updated_at":{"description":"When the destination last changed; null if never.","format":"date-time","type":["string","null"]},"target_url":{"format":"uri","type":"string"},"utm":{"additionalProperties":{"type":"string"},"description":"The campaign tags in the destination, without the `utm_` prefix.","type":"object"}},"required":["code","short_url","target_url","utm","status","created_at","clicks","qr_scans","owned","decode_url","qr"],"type":"object"},"LinkStats":{"allOf":[{"$ref":"#/components/schemas/Link"},{"properties":{"breakdown":{"description":"All time, top 10 of each, most clicks first.","properties":{"country":{"items":{"$ref":"#/components/schemas/Count"},"type":"array"},"device":{"items":{"$ref":"#/components/schemas/Count"},"type":"array"},"referrer":{"items":{"$ref":"#/components/schemas/Count"},"type":"array"}},"required":["referrer","country","device"],"type":"object"},"days":{"description":"Oldest first, every day of the period, zeros included.","items":{"$ref":"#/components/schemas/Day"},"type":"array"},"history":{"description":"Earlier destinations, newest first. Empty if it never changed.","items":{"properties":{"replaced_at":{"format":"date-time","type":"string"},"target_url":{"format":"uri","type":"string"}},"required":["target_url","replaced_at"],"type":"object"},"type":"array"}},"required":["days","breakdown","history"],"type":"object"}]},"NewKey":{"properties":{"label":{"description":"A name to remember the key by, such as `newsletter script`. Up to 80 characters; longer is cut. Shown back in `GET /keys/current`.","maxLength":80,"type":"string"}},"type":"object"},"NewKeyCreated":{"allOf":[{"$ref":"#/components/schemas/Key"},{"properties":{"key":{"description":"The key. Shown this once.","type":"string"}},"required":["key"],"type":"object"}]},"NewLink":{"properties":{"alias":{"description":"Your own code instead of a random 7-character one: 3 to 40 letters, digits or hyphens, not starting or ending with a hyphen. Stored lowercase. `409 alias_taken` if it is in use, or belonged to a deleted link.","pattern":"^[A-Za-z0-9][A-Za-z0-9-]{1,38}[A-Za-z0-9]$","type":"string"},"url":{"description":"Where the link goes: an `http://` or `https://` address, up to 4,096 characters with its tags. `https://` is added if there is no scheme. Refused: private or local addresses, addresses with a user name or password in them, other link shorteners, NOR Bit itself, and addresses Google Safe Browsing lists as harmful.","maxLength":4096,"type":"string"},"utm":{"$ref":"#/components/schemas/Utm","description":"Campaign tags to add to the destination, so its own analytics can tell where visitors came from. Each replaces a tag of the same name already in `url`."}},"required":["url"],"type":"object"},"UpdateLink":{"minProperties":1,"properties":{"url":{"description":"The new destination, with the same rules as creating a link. Leave it out to keep the current one, for example to change only the tags.","maxLength":4096,"type":"string"},"utm":{"$ref":"#/components/schemas/Utm","description":"Campaign tags to set on the destination: on the new `url` if you send one, otherwise on the current destination."}},"type":"object"},"Utm":{"additionalProperties":false,"description":"Campaign tags, written into the destination as `utm_source`, `utm_medium` and so on. `utm_source` is accepted as a name too. Empty or null: left out.","examples":[{"campaign":"autumn-sale","medium":"email","source":"newsletter"}],"properties":{"campaign":{"description":"The campaign it belongs to: `autumn-sale`. Written as `utm_campaign`.","maxLength":200,"type":["string","null"]},"content":{"description":"Which link, button or ad, to tell apart links in the same campaign: `header-button`. Written as `utm_content`.","maxLength":200,"type":["string","null"]},"medium":{"description":"The kind of channel: `email`, `social`, `qr`, `cpc`. Written as `utm_medium`.","maxLength":200,"type":["string","null"]},"source":{"description":"Where visitors come from: `newsletter`, `facebook`, `qr-poster`. Written as `utm_source`.","maxLength":200,"type":["string","null"]},"term":{"description":"The paid search keyword, if there is one. Written as `utm_term`.","maxLength":200,"type":["string","null"]}},"type":"object"}},"securitySchemes":{"apiKey":{"bearerFormat":"nbk_<43 characters>","description":"Get one with POST /keys.","scheme":"bearer","type":"http"}}},"externalDocs":{"description":"Developers page","url":"https://bit.normake.com/developers"},"info":{"contact":{"name":"NOR Bit","url":"https://bit.normake.com/developers"},"description":"Free. Get a key with `POST /keys` (or on the developers page), then send it as `Authorization: Bearer nbk_...` on every other request.\n\nLinks you create belong to your key: only that key sees their detailed statistics. Anyone can decode any link and see its total clicks.\n\nKeys are never deleted. `DELETE /keys/current` marks a key deleted: it stops working at once, and the links it made keep redirecting. Links can be deleted (`DELETE /links/{code}`), and their codes are never reused.\n\nLimits are per key per hour (see `GET /keys/current`), and new links also count per connection, whatever key they use. Over a limit you get `429` with a `Retry-After` header. Errors are always `{\"error\": \"...\", \"message\": \"...\"}`.","summary":"Short links at normake.com/b/, with QR codes and click counts.","termsOfService":"https://bit.normake.com/terms","title":"NOR Bit API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/keys":{"post":{"description":"The key is in the response **once**: store it, it cannot be shown again. Up to 3 keys per connection per day.","operationId":"createKey","requestBody":{"content":{"application/json":{"example":{"label":"newsletter script"},"schema":{"$ref":"#/components/schemas/NewKey"}}},"required":false},"responses":{"201":{"content":{"application/json":{"example":{"created_at":"2026-10-03T09:00:00Z","key":"nbk_Q2x9vT4a8YbN1cR6dE3fG7hJ0kL5mP9sU2wX4zA","label":"newsletter script","last_used_at":null,"limits":{"links_per_hour":100,"requests_per_hour":1000},"links_created":0,"prefix":"nbk_Q2x9vT4a","revoked_at":null,"status":"active"},"schema":{"$ref":"#/components/schemas/NewKeyCreated"}}},"description":"The new key, with `key` set."},"429":{"$ref":"#/components/responses/RateLimited"}},"security":[],"summary":"Get an API key","tags":["Keys"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X POST https://bit.normake.com/api/v1/keys \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"label\": \"my script\"}'"}]}},"/keys/current":{"delete":{"description":"The key stops working immediately and cannot be restored. It is kept, marked deleted, never removed. Links it made keep redirecting and keep counting clicks; their manage links still work in a browser.","operationId":"revokeCurrentKey","responses":{"200":{"content":{"application/json":{"example":{"created_at":"2026-10-03T09:00:00Z","label":"newsletter script","last_used_at":"2026-10-03T09:15:00Z","limits":{"links_per_hour":100,"requests_per_hour":1000},"links_created":12,"prefix":"nbk_Q2x9vT4a","revoked_at":"2026-10-04T08:00:00Z","status":"revoked"},"schema":{"$ref":"#/components/schemas/Key"}}},"description":"The key, now `revoked`."},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Mark this key deleted","tags":["Keys"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X DELETE https://bit.normake.com/api/v1/keys/current \\\n  -H \"Authorization: Bearer $NORBIT_KEY\""}]},"get":{"description":"Its status, limits, and how many links it has made.","operationId":"getCurrentKey","responses":{"200":{"content":{"application/json":{"example":{"created_at":"2026-10-03T09:00:00Z","label":"newsletter script","last_used_at":"2026-10-03T09:15:00Z","limits":{"links_per_hour":100,"requests_per_hour":1000},"links_created":12,"prefix":"nbk_Q2x9vT4a","revoked_at":null,"status":"active"},"schema":{"$ref":"#/components/schemas/Key"}}},"description":"The key."},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"This key","tags":["Keys"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X GET https://bit.normake.com/api/v1/keys/current \\\n  -H \"Authorization: Bearer $NORBIT_KEY\""}]}},"/links":{"post":{"description":"The link belongs to your key. `manage_url` opens its page with the owner's controls (pause, delete) in a browser; keep it private. What each field accepts, and what is refused, is in the table below.","operationId":"createLink","requestBody":{"content":{"application/json":{"example":{"alias":"my-page","url":"https://example.com/a/very/long/page","utm":{"medium":"email","source":"newsletter"}},"schema":{"$ref":"#/components/schemas/NewLink"}}},"required":true},"responses":{"201":{"content":{"application/json":{"example":{"clicks":0,"code":"k7x2m9q","created_at":"2026-10-03T09:15:00Z","decode_url":"https://bit.normake.com/d/k7x2m9q","last_click_at":null,"manage_url":"https://bit.normake.com/d/k7x2m9q#key=...","owned":true,"qr":{"png":"https://bit.normake.com/b/k7x2m9q/qr.png","svg":"https://bit.normake.com/b/k7x2m9q/qr.svg"},"qr_scans":0,"short_url":"https://normake.com/b/k7x2m9q","status":"active","target_updated_at":null,"target_url":"https://example.com/a/very/long/page?utm_source=newsletter&utm_medium=email","utm":{"medium":"email","source":"newsletter"}},"schema":{"$ref":"#/components/schemas/CreatedLink"}}},"description":"The link."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `missing_url`, `bad_url`, `bad_scheme`, `bad_alias`, `bad_utm`, `too_long`, `private_address`, `loop`, `shortener`, `blocked`, `unsafe_url`."},"401":{"$ref":"#/components/responses/Unauthorized"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `alias_taken`."},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Create a short link","tags":["Links"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X POST https://bit.normake.com/api/v1/links \\\n  -H \"Authorization: Bearer $NORBIT_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"url\": \"https://example.com/a/very/long/page\"}'"}]}},"/links/{code}":{"delete":{"description":"Permanent: the link stops redirecting and its statistics are deleted with it. Only the key that made the link can delete it.\n\nIts code is **retired**, never given to anyone else: anyone who still has the old link (on a poster, in a QR code, in a message) is told it was deleted, instead of being sent somewhere new. A link that was reported or switched off can't be deleted (`409 link_disabled`).","operationId":"deleteLink","parameters":[{"description":"The part after `/b/`. Case-insensitive.","in":"path","name":"code","required":true,"schema":{"examples":["k7x2m9q"],"pattern":"^[A-Za-z0-9-]{3,40}$","type":"string"}}],"responses":{"204":{"description":"Deleted. No body."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `not_owner`."},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `link_disabled`."},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Delete one of your links","tags":["Links"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X DELETE https://bit.normake.com/api/v1/links/k7x2m9q \\\n  -H \"Authorization: Bearer $NORBIT_KEY\""}]},"get":{"description":"Where any NOR Bit link goes, its status and its total clicks. Works for every link, not only your own; `owned` says whether your key made it.","operationId":"decodeLink","parameters":[{"description":"The part after `/b/`. Case-insensitive.","in":"path","name":"code","required":true,"schema":{"examples":["k7x2m9q"],"pattern":"^[A-Za-z0-9-]{3,40}$","type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"clicks":42,"code":"k7x2m9q","created_at":"2026-10-03T09:15:00Z","decode_url":"https://bit.normake.com/d/k7x2m9q","last_click_at":"2026-10-03T11:02:00Z","owned":true,"qr":{"png":"https://bit.normake.com/b/k7x2m9q/qr.png","svg":"https://bit.normake.com/b/k7x2m9q/qr.svg"},"qr_scans":17,"short_url":"https://normake.com/b/k7x2m9q","status":"active","target_updated_at":null,"target_url":"https://example.com/a/very/long/page?utm_source=newsletter&utm_medium=email","utm":{"medium":"email","source":"newsletter"}},"schema":{"$ref":"#/components/schemas/Link"}}},"description":"The link."},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Decode a link","tags":["Links"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X GET https://bit.normake.com/api/v1/links/k7x2m9q \\\n  -H \"Authorization: Bearer $NORBIT_KEY\""}]},"patch":{"description":"For a page that moved: the short link, its QR code, its clicks and statistics all stay the same, and it redirects to the new address at once. Only the key that made the link can change it, and the same checks as creating apply.\n\nSend `url`, `utm`, or both. `utm` alone retags the current destination; with `url`, the tags are added to the new one.\n\nChanges are public in outline: `target_updated_at` shows when the destination last changed, on the link's page too. Every earlier destination is kept, in `history` from `GET /links/{code}/stats`. Sending the current destination changes nothing. A link switched off for abuse cannot be changed.","operationId":"updateLink","parameters":[{"description":"The part after `/b/`. Case-insensitive.","in":"path","name":"code","required":true,"schema":{"examples":["k7x2m9q"],"pattern":"^[A-Za-z0-9-]{3,40}$","type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"url":"https://example.com/the/page/moved/here"},"schema":{"$ref":"#/components/schemas/UpdateLink"}}},"required":true},"responses":{"200":{"content":{"application/json":{"example":{"clicks":42,"code":"k7x2m9q","created_at":"2026-10-03T09:15:00Z","decode_url":"https://bit.normake.com/d/k7x2m9q","last_click_at":"2026-10-03T11:02:00Z","owned":true,"qr":{"png":"https://bit.normake.com/b/k7x2m9q/qr.png","svg":"https://bit.normake.com/b/k7x2m9q/qr.svg"},"qr_scans":17,"short_url":"https://normake.com/b/k7x2m9q","status":"active","target_updated_at":"2026-10-05T14:20:00Z","target_url":"https://example.com/the/page/moved/here","utm":{}},"schema":{"$ref":"#/components/schemas/Link"}}},"description":"The link, going to its new destination."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `missing_url`, `bad_url`, `bad_scheme`, `bad_utm`, `too_long`, `private_address`, `loop`, `shortener`, `blocked`, `unsafe_url`."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `not_owner`."},"404":{"$ref":"#/components/responses/NotFound"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `link_disabled`."},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Change where one of your links goes, or its campaign tags","tags":["Links"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X PATCH https://bit.normake.com/api/v1/links/k7x2m9q \\\n  -H \"Authorization: Bearer $NORBIT_KEY\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"url\": \"https://example.com/the/page/moved/here\"}'"}]}},"/links/{code}/stats":{"get":{"description":"Clicks per day (QR scans included and also counted separately), and all-time clicks by referring site, country and device. Only for links your key made.\n\nLink-preview bots (WhatsApp, Messenger, Slack, \u2026) are never counted. Nothing identifies a visitor: no IP addresses are stored.","operationId":"linkStats","parameters":[{"description":"The part after `/b/`. Case-insensitive.","in":"path","name":"code","required":true,"schema":{"examples":["k7x2m9q"],"pattern":"^[A-Za-z0-9-]{3,40}$","type":"string"}},{"description":"How many days of `days` to return. 0 is all time.","in":"query","name":"days","required":false,"schema":{"default":30,"enum":[7,30,90,365,0],"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"breakdown":{"country":[{"clicks":38,"value":"RO"},{"clicks":4,"value":"DE"}],"device":[{"clicks":35,"value":"phone"},{"clicks":7,"value":"desktop"}],"referrer":[{"clicks":25,"value":"facebook.com"},{"clicks":17,"value":""}]},"clicks":42,"code":"k7x2m9q","created_at":"2026-10-03T09:15:00Z","days":[{"clicks":30,"day":"2026-10-02","qr":12},{"clicks":12,"day":"2026-10-03","qr":5}],"decode_url":"https://bit.normake.com/d/k7x2m9q","history":[],"last_click_at":"2026-10-03T11:02:00Z","owned":true,"qr":{"png":"https://bit.normake.com/b/k7x2m9q/qr.png","svg":"https://bit.normake.com/b/k7x2m9q/qr.svg"},"qr_scans":17,"short_url":"https://normake.com/b/k7x2m9q","status":"active","target_updated_at":null,"target_url":"https://example.com/a/very/long/page?utm_source=newsletter&utm_medium=email","utm":{"medium":"email","source":"newsletter"}},"schema":{"$ref":"#/components/schemas/LinkStats"}}},"description":"The link and its statistics."},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `bad_days`."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error. `error` is one of: `not_owner`."},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Statistics for one of your links","tags":["Links"],"x-codeSamples":[{"label":"curl","lang":"shell","source":"curl -X GET https://bit.normake.com/api/v1/links/k7x2m9q/stats?days=90 \\\n  -H \"Authorization: Bearer $NORBIT_KEY\""}]}},"/openapi.json":{"get":{"description":"OpenAPI 3.1. Import it into Postman, Insomnia or a client generator.","operationId":"openapi","responses":{"200":{"content":{"application/json":{"schema":{"type":"object"}}},"description":"The OpenAPI document."}},"security":[],"summary":"This description","tags":["Keys"]}}},"security":[{"apiKey":[]}],"servers":[{"url":"https://bit.normake.com/api/v1"}],"tags":[{"description":"Get a key, see it, mark it deleted.","name":"Keys"},{"description":"Create, decode, statistics.","name":"Links"}]}
