Skip to main content

API keys and the REST API

Create a key, call the API, and understand scopes, rate limits, and idempotency.

Reviewed 24 Oct 2026

Open API keysIn your workspace. You are asked to log in first if you are not already.

What the API is for

Anything you do by hand in the dashboard, you can do from your own code: create links when a product is published, pull click numbers into an internal report, tidy up links when a campaign ends.

The current surface covers links, per-link analytics, and conversion events.

EndpointDoes
GET /api/v1/linksList your links. Takes cursor, limit, and search; returns items with nextCursor and hasMore.
POST /api/v1/linksCreate a link.
GET /api/v1/links/{id}Fetch one link.
PATCH /api/v1/links/{id}Update a link. Can set or clear the same protection and organisation fields as POST (password, expiry, click cap, fallback URL, domain, folder, UTM).
DELETE /api/v1/links/{id}Delete a link.
GET /api/v1/analytics/{linkId}Analytics for one link.
POST /api/v1/conversionsRecord a conversion event. Needs conversions:write.
GET /api/v1/openapiThe full machine-readable specification.

The OpenAPI document is the authoritative reference: exact parameters, response shapes, and error codes. It is also what you feed to a client generator rather than writing request code by hand.

Keys

Create keys on the Developer page. Authenticate by sending the key as a bearer token.

curl https://shortzen.io/api/v1/links \
  -H "Authorization: Bearer sz_live_<your-api-key>"

The key is shown once

We store only a hash of it, so we genuinely cannot show it to you again. That is also why a leak of our database is not a leak of your keys. Copy it into your secret store at the moment of creation. Lost a key? Revoke it and make another.

Each key also records the workspace it acts on: the one you were working in when you created it. Older keys may show "Workspace not recorded" instead. That does not stop the key working, and it will not fill in later: to bind a key to a workspace, revoke it and create a new one while working in that workspace.

You can hold at most 20 live keys at a time. A key also stops authenticating on its own once it turns 365 days old, whether or not you set an expiry date. About 7 days before that effective expiry we email the holder. There is no renewal: create a new key, switch the integration, then revoke the old one.

The API keys page lists the keys you created under "Your keys". Workspace owners and admins also get an "All in this workspace" tab beside it, listing every member's keys with a revoke control. You can revoke your own key at any time. Removing someone's workspace access does not revoke their keys, so treat a key you cannot personally reach as still live until its holder revokes it.

  • Do not commit a key to source control, and do not put one in front-end code: anything in a browser is public.
  • Use environment variables or a secret manager.
  • One key per integration, so you can revoke one thing without breaking the others.
  • Revoke immediately if a key may have been exposed. Revocation takes effect at once.

Scopes

A key can be limited to what it actually needs.

ScopeAllows
links:readListing and fetching links.
links:writeCreating and updating links, and pausing or resuming them.
links:deleteDeleting links. Not included in links:write; grant it only to a key that must remove links.
analytics:readReading analytics.
workspace:readListing domains and reading plan usage.
conversions:writeRecording conversion events.
*Everything.

Give the least you can

A reporting job needs links:read and analytics:read and nothing else. Scoped that way, the worst case if the key leaks is that someone reads numbers, not that they rewrite every destination you have.

Rate limits

The hourly request budget belongs to the workspace, not to each key. Every key in the workspace draws on the same allowance, so issuing more keys does not buy more requests: splitting one integration across several keys will not raise the ceiling.

API calls / hour, by plan
Free 300 · Starter 1,200 · Pro 5,000 · Business 20,000

Handle the limit rather than trying to stay under it by luck: on a rate-limit response, back off and retry after a delay that grows each time. A job that retries immediately in a loop will simply stay limited.

Idempotency

Send your own value in an Idempotency-Key header on a create request and retrying it will not create a second link. Use it whenever a network timeout would otherwise leave you unsure whether the first attempt succeeded.

curl -X POST https://shortzen.io/api/v1/links \
  -H "Authorization: Bearer sz_live_<your-api-key>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f9b1c84-3a5e-4d21-9c7f-0b2e8a41d5cc" \
  -d '{"destinationUrl": "https://example.com/spring-sale"}'

Repeat that request with the same key and you get the first response back, replayed rather than run again. Repeat it while the first attempt is still in flight and you get a 409 telling you to retry shortly. Send the same key with a different body and it is refused with a 422: the key is bound to the body it was first used with, so a changed payload is a different operation reusing a spent key, not a retry.

Generate a fresh value per logical operation: a UUID, or something derived from the record you are creating a link for.

Use with AI assistants

An AI assistant that supports the Model Context Protocol (MCP) can read and manage your links, with your role, your plan and your quota applied as they are here. Most assistants log in rather than use a key; Cursor and your own scripts use one. Connect an AI assistant has the steps for each assistant, how to check it works, and what to do when it does not.

Legacy keys that carry no scopes

A key minted before scopes were required carries an empty scope list. That empty list used to be read as full access. It is now read as what it literally says (no scope was granted), so the key still authenticates and is then refused at the scope check on every endpoint that takes a key. The symptom is an integration that ran for months suddenly getting 403s on calls it has made from the start, with an INSUFFICIENT_SCOPE code and nothing changed at your end.

You can spot one on the Developer page: every other key names its scopes or reads "Full access", and one of these has a blank scope badge. If you were sent the in-app notification headed "One of your API keys has stopped working", it is pointing at exactly this.

A legacy key cannot be repaired, only replaced

Scopes are fixed when the key is created and there is no way to add them afterwards. Revoke the key, create a replacement with the scopes that integration actually needs, and deploy the new one. Keys created today cannot end up in this state: the create form and the API both refuse a key with no scopes.

Step by step

Create your first working integration

For: Developers, on every plan including Free

Before you start

  • A plan that includes API access.
  • Somewhere safe to store a secret.
  • A way to make HTTP requests.

Steps

  1. Open Developer and create an API key.

  2. Give it only the scopes this integration needs.

    links:write alone if it only creates links; add links:delete only if it must remove them. Resist reaching for the wildcard.

  3. Copy the key straight into your secret store.

    Not into a scratch file, not into chat. This is the only time you will see it.

    The key is stored where your application can read it and a person cannot.

  4. Make a read request first to prove authentication works.

    Start with a GET. If it fails you know the problem is the key, not your payload.

    You get back a list of your links.

  5. Fetch the OpenAPI document and read the shape of the create endpoint.

    You know exactly which fields to send.

  6. Create one test link, with an idempotency key.

    A 2xx response containing the new link.

  7. Confirm it appears in your dashboard Links table.

    This proves the whole path, not just that the request was accepted.

  8. Add retry-with-backoff for rate-limit and server-error responses before you go live.

What you should see

A working, least-privilege integration that creates links safely and survives being retried.

How to check it worked

  • Requests authenticate and return the data you expect.
  • Links created through the API appear in the dashboard.
  • Repeating a create with the same idempotency key does not produce a duplicate.

Edge cases

You get an authentication error.
Check the header is exactly "Authorization: Bearer <key>", and that the key has not been revoked, expired, or turned 365 days old: all four read back the same generic error.
You get a permission error on a call that should work.
The key is missing that scope. Scopes are fixed at creation, so make a new key with the right ones.
You are being rate-limited.
You are over your workspace's hourly allowance, which every key shares. Back off, batch your work, or upgrade.
Your plan does not include API access.
The API keys page shows an upgrade notice in place of the list. That happens only where a workspace has had API access withdrawn. Every plan, Free included, carries it; what differs is the hourly rate.

If something goes wrong

A key has leaked.
If you hold the key, revoke it now from the Developer page and create a replacement; revocation is immediate. Then work out what it did: the key's last-used stamp (or "Never used") tells you whether it authenticated at all and when it last did, and the Links page (newest first) shows anything created in that window. On Business, the Audit Logs page answers it directly instead of leaving you to infer it. Removing a holder's workspace access does not revoke the key itself.
A key shows "Workspace not recorded" on the Developer page.
Nothing will fill that in for you. If you need the key tied to a workspace, revoke it and create a replacement while working in that workspace, then deploy the new key.
A retry created duplicate links.
Delete the duplicates and add an idempotency key to that call. This is precisely the problem it exists to prevent.

Try it on a real link

The free plan needs no card and has no time limit.