Getting started
From an invitation to a link on a poster.
You need an account, and accounts here are made by the person who runs the box — there is no open sign-up unless they have turned it on. What arrives is an invitation link that works once.
Setting up
- Open the invitation, pick the name you want to sign in with, and set a pass phrase of at least 12 characters.
- Your name is also your namespace, so choose one you are happy to see on the front of every link you make. It can be changed later, with a wait between changes, and the old one is never given to anybody else.
- Set up a six-digit code from an app on your phone. Keep the ten backup codes it shows you: there is no email address on file here, so those codes are the way back in if the phone goes.
- Add a passkey afterwards if the device supports one. It is one step instead of two, and a passkey that asks for a fingerprint or a PIN is already two factors on its own.
- The Appearance button (top right) also has Color vision: Protanopia, Deuteranopia and Tritanopia each swap the state colors, and the link color where it would clash, for a set tuned and tested for that type. Every state also has a word or a shape, so nothing depends on color alone. Like the theme, it is kept in this browser only.
- Appearance → Cursor: the pointer is drawn in your theme's colors. Tick Use the system cursor to keep your own, which also keeps your operating system's pointer size and color settings. High contrast, e-ink, Windows High Contrast and touch screens always use the system cursor.
Your first link
Paste a URL into Target URL on the dashboard and press Shorten. You get back https://geodzk.com/l/your-name/something, copied to the clipboard and ready to send. Leave Code empty for a short random one, or type your own.
curl -s -X POST https://geodzk.com/api/links \
-H "X-API-Key: $GEODZK_KEY" -H 'Content-Type: application/json' \
-d '{"url":"https://example.com/hello","code":"hello"}'
Codes, and why they never come back
- A code is one to sixty-four characters of
A-Za-z0-9_-. Omit it and you get a random one of 5 characters. - Delete a link and its code is retired, not freed. That address will never point at anything again, for you or for anybody else — because the old one may be on a poster somebody still has.
- So never delete a link to tidy up. Retarget it instead, or let it expire.
- If you want a retired code, you can ask the operator to release it, and they can. Nobody else can take it in the meantime.
- The bare space at
https://geodzk.com/l/codebelongs to the operator. Short, memorable top-level codes are held from the start so they stay available for the service's own use rather than going to whoever asked first.
What a link can be told to do
| Field | Effect |
|---|---|
title | A label for your own list. Never shown to a visitor. |
group_id | Put it in one of your groups, which is what a collection is made of. |
active_from | Not live until then. Before that it answers as if it does not exist. |
expires_at | Dead after then. |
max_clicks | Works a set number of times, then stops. If the count cannot be written, it is held back rather than served. |
fallback_url | Where an expired or used-up link goes instead of a dead end. Worth setting on anything printed. |
Channels are the one way to learn where a link's visits come from without learning anything about the visitors. Register tags with PUT /api/links/{code}/channels — poster, sheet, newsletter — and put one in the address you hand out: …/l/alice/party?via=poster. Each visit is counted against the tag. Nothing is read from the visitor; the tag names the poster, never the scanner. A tag nobody registered counts nothing, so a stranger cannot invent one. /print/l/{code}?via=poster puts the tag into the printed QR so a whole print run is counted as one channel.
When a link is followed is kept as 168 counters — weekday × hour, UTC — bumped at the click; no timestamp is written and the click's own second goes no further than that statement. GET /api/links/{code}/when releases them only above thresholds: nothing at all under 20 visits, and any cell under 3 as few rather than a number, because a cell of one is a person when you know who you handed the link to. The dashboard draws it as the field's own nodes. Beside it, readings: what those counters can say in a line each — quiet since, weekday share, busiest hours, how each printed tag did, refusals on a capped link — arithmetic under the same thresholds, nothing stored to produce it. ?further=1 computes the guesses (the crowd's time zone, which tag is ahead as ranges, next week from the average) only when asked; each refuses below its own floor and says in words that it is about everyone at once and never anyone.
What a visitor sees when a link does not work
None of these say whether a code ever existed, who owned it, or why it stopped. The recipient's next step is the same in every case, which is to go back to whoever sent it.
| Situation | The page says |
|---|---|
| Never a link here | That link doesn't exist |
| Deleted by its owner | That link has been retired |
| Held by the operator | That one is taken |
| Past its expiry | That link has expired |
| Used up its clicks | That link has been used up |
| Turned off by an administrator | That link has been turned off |
Your ceilings
Every account has a quota, and the dashboard shows where you are against it. Nought means no limit, never none allowed.
| Ceiling | Default | Set by |
|---|---|---|
| Links per account | 500 | dashboard, and per account |
| Groups per account | 25 | dashboard, and per account |
| Links shown on one collection | 500 | environment |
| Live API keys per account | 20 | environment |
| Keys an account may ever mint | 100 | environment |
| Passkeys per account | 10 | environment |
| Accounts on the instance | 100 | dashboard |
| Links on the instance | 50,000 | dashboard |
| Masked links on the instance | 5,000 | environment |
| Target URL length | 2,048 characters | environment |
| Code length | 5 random, up to 64 chosen | environment |
| Session lifetime | 14 days | environment |
| Invitation lifetime | 14 days | per invitation |
| Wait between username changes | 90 days | environment |
| Shortest password | 12 characters | environment |
The same page as text, for a program: GET https://geodzk.com/api/docs/start — needs a key holding docs:read.