Blog
Link shortener API tutorial: from first key to a working integration
Create a key with the right scopes, shorten a link, read its stats, and handle the four responses that will actually break your integration. With the real request and response shapes.
By Wasim Idrishi · Updated · 5 min read
The fastest useful thing you can do with the API is turn a row in your database into a short link with a QR code. That takes one request. Everything after it is error handling, which is the part that decides whether the integration survives a month.
Get a key with the scopes you need
Keys are created from the dashboard by a workspace admin — an API key cannot create another API key, which is the point.
Ask for the narrowest scopes that do the job. There are eight: links:read, links:write,
analytics:read, domains:read, domains:write, names:write, pages:write, billing:read. A key that
only shortens URLs wants links:write and nothing else.
Three options are worth setting at creation:
Environment. gc_test_ keys are visually distinct from gc_live_ keys in a log, a screenshot and a
config file. Use the test prefix in staging so a wrong environment variable is obvious rather than silent.
IP allowlist. Up to 20 addresses. If your integration runs from fixed egress addresses, this turns a leaked key into a useless string.
Expiry. A key with an expiry date is a key you will rotate. A key without one is a key that outlives the person who created it.
The first request
Every call takes a bearer token:
POST /api/v1/links
Authorization: Bearer gc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{"url": "https://example.com/a/very/long/product/page?variant=42"}
A 201 comes back with the link. The fields you will actually use are shortUrl, code, qrUrl (the
short URL with .qr appended, which serves a PNG) and statsUrl (the short URL with + appended). Store
the id — it is what the update, delete and stats endpoints take.
Add anything else in the same call: alias for a custom word, title and tags for your own organisation,
utm for campaign defaults, expiresAt, maxClicks, redirectType, password, rules for targeting.
The four responses that will break you
| Status | code | Cause | What your client should do |
|---|---|---|---|
| 400 | validation_error | Input failed the schema | Fix and do not retry; details names the field |
| 402 | upgrade_required | Plan limit or gated feature | Surface an upgrade path, do not retry |
| 409 | conflict | The alias is taken | Choose another alias |
| 429 | rate_limited | Window or daily allowance exhausted | Sleep for Retry-After seconds, then retry |
Two habits make this painless. Branch on code, never on the human sentence in error — the sentence is
written for a person and will be improved. And log request_id from every failed response; it is on every
reply as a header and in every error body, and it is what we search on when you ask what happened.
Reading stats
GET /api/v1/links/{id}/stats
Authorization: Bearer gc_live_...
Needs analytics:read. You get totals — clicks, uniques, bots, scans — a daily series, and breakdowns by
country, city, referrer, referrer type, device, operating system, browser, language, campaign and A/B
variant.
The range is clamped to your plan's retention: 30 days on Free, 365 on Pro, 1,095 on Business and Team. Ask for a wider window and you get the widest you are allowed, not an error. The response says which tier and how many days it applied, so your client never has to hard-code that.
For a whole workspace at once, GET /api/v1/analytics takes from, to, interval and a groupBy, and
returns one breakdown instead of every table — cheaper to fetch and easier to chart.
Doing it in volume
Batch instead of looping
POST /api/v1/links/bulk takes up to 1,000 rows and returns a per-row result keyed by index. A loop of 1,000 single requests will hit a rate limit; one batch will not.
Treat the response as a ledger
Rows succeed and fail independently. Join the results back onto your input by index and retry only the rows that failed, with the reason each one gives.
Read the rate-limit headers you already receive
X-RateLimit-Remaining is on every response. Slow down when it falls rather than waiting for a 429.
Export rather than paginate for reporting
The CSV export streams the link inventory or the daily rollups. Pulling three years of stats one link at a time is the wrong shape and will take all afternoon.
Bulk URL Shortener
Open the full toolBefore you ship
Rotate the key you developed with — it has been in your shell history and probably in a screenshot. Set an
expiry on the replacement. Check that a 402 in your code path shows something a human can act on. And point
the integration at a gc_test_ key in staging so the day someone copies the wrong environment file, nothing
real is created.
The links your integration creates are ordinary branded links: editable destinations, per
link analytics, a QR at .qr or .svg, and no expiry on a paid plan. Nothing about them is different
because a program made them.
Questions people ask
How do I authenticate?
A bearer token in the Authorization header. Keys look like gc_live_ or gc_test_ followed by 32 characters. Only a SHA-256 hash of the key is stored, so the full value is shown once at creation and cannot be retrieved afterwards — if it is lost, revoke it and make another.
Can an API key create another API key?
No. Key management is a session-only operation for a workspace admin, and so is webhook management. A leaked key cannot be used to mint more keys or to redirect your event deliveries.
What is the rate limit?
A daily allowance set by plan — none on Free, 10,000 requests a day on Pro, 100,000 on Business and Team — plus a per-endpoint window. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining, and a 429 carries Retry-After in seconds.
What does a 402 mean?
A plan limit or a feature gate, not a payment failure. The body names the feature and the plan that includes it, so a client can route straight to an upgrade screen instead of showing a generic error.
How do I scope a key down?
Pass a scopes array at creation. Eight scopes exist: links read and write, analytics read, domains read and write, names write, pages write, and billing read. A key that only creates links needs links:write and nothing else.
Keep going
Try it with your own link
Paste a long link and get a 10-character gc.mw link with a QR code. No account needed for 30 days; free forever with one.