Skip to Content
Retargeting Pixels on every Link 🎉
Link Squeeze LogoLink Squeeze Docs
FeaturesToolsPricingBlog
CTRL K
CTRL K
    • How to Add Custom Domains to Link Squeeze
    • How to Shorten Links Using Link Squeeze
    • How to Shorten Shopify URLs Using Link Squeeze
    • How to Add Google Ads Tag to Link Squeeze
    • How to Add Meta Pixel to Link Squeeze
    • How to Add Retargeting Pixels to links in Link Squeeze
    • How to Create a Custom Audience on Facebook and Instagram
    • Authentication
    • Links
    • Click Analytics
    • Domains
    • Pixels
    • Webhooks
    • Errors & Rate Limits
  • Features
  • Tools
  • Pricing
  • Blog
    • How to Add Custom Domains to Link Squeeze
    • How to Shorten Links Using Link Squeeze
    • How to Shorten Shopify URLs Using Link Squeeze
    • How to Add Google Ads Tag to Link Squeeze
    • How to Add Meta Pixel to Link Squeeze
    • How to Add Retargeting Pixels to links in Link Squeeze
    • How to Create a Custom Audience on Facebook and Instagram
    • Authentication
    • Links
    • Click Analytics
    • Domains
    • Pixels
    • Webhooks
    • Errors & Rate Limits

On This Page

  • The error envelope
  • Status codes
  • Success
  • Errors
  • Codes in practice
  • 401 vs 403
  • 402 payment_required
  • 404 not_found
  • 422 validation_error
  • Rate limits
  • Reading your budget
  • When you exceed it
  • Staying under the limit
Question? Give us feedback
API ReferenceErrors & Rate Limits

Errors & rate limits

The error envelope

Every error returns the same JSON shape, with a stable machine-readable code and a human-readable message:

{ "error": { "code": "not_found", "message": "The requested resource could not be found." } }

Branch on code, not on message. Messages are written for humans and may be reworded; codes are part of the contract.

Validation failures add an errors object keyed by field name, each holding an array of messages:

{ "error": { "code": "validation_error", "message": "The given data was invalid.", "errors": { "destination_url": ["The destination url field must be a valid URL."], "expires_at": ["The expires at field must be a date after now."] } } }

Always send Accept: application/json. Without it, some framework-level failures can be rendered as HTML rather than the envelope above.

Status codes

Success

StatusWhen
200 OKA successful read or update
201 CreatedA resource was created
202 AcceptedWork was accepted but completes asynchronously — domain verification
204 No ContentA successful delete; the body is empty

Errors

StatuscodeMeaning
401 UnauthorizedunauthenticatedNo API key, or a key that has been revoked or expired
402 Payment Requiredpayment_requiredThe account has no active plan
403 ForbiddenforbiddenThe key is valid but lacks the scope this endpoint requires
404 Not Foundnot_foundNo such resource — or it belongs to another account
405 Method Not Allowedmethod_not_allowedWrong HTTP verb for that path
422 Unprocessable Entityvalidation_errorThe request body failed validation; see errors
429 Too Many Requestsrate_limitedThe rate limit was exceeded
500 Internal Server Errorserver_errorSomething went wrong on our side

Other HTTP failures fall back to the generic code http_error.

Codes in practice

401 vs 403

These two are easy to confuse and mean quite different things:

  • 401 unauthenticated — we could not identify you at all. The Authorization header is missing, malformed, or the key has been revoked or has expired. Check the credential.
  • 403 forbidden — we know exactly who you are, and this key is not allowed to do that. The key was issued without the required scope. Generate a new key with the scope included; scopes cannot be added to an existing key.

402 payment_required

The API requires an active plan. This check runs after authentication, so a bad key returns 401 even on an unsubscribed account.

Access is granted for a lifetime purchase, a running trial, or a live subscription. A subscription that has lapsed into past_due — after a card failure, for example — returns 402 until billing is resolved.

404 not_found

Returned both for a resource that does not exist and for one that exists on another account. The API deliberately does not distinguish between the two, so it cannot be used to probe for valid ids belonging to other customers.

If a 404 is unexpected, check that you are using the resource’s UUID and not a numeric id or a slug.

422 validation_error

The most common cause of a 422 on writes:

FieldCommon cause
slugAlready taken. Slugs are unique across all of Link Squeeze, not just your account.
hostname (links)Not a domain registered to your account.
hostname (domains)Not a valid hostname, or already registered on Link Squeeze.
destination_urlMissing on a PATCH that also sends utm_params — the two must be sent together.
expires_atNot in the future.
pixel_ids.*A pixel UUID not registered to your account.
platformNot one of facebook, tiktok, pinterest, google_ads.
url (webhooks)Not HTTPS, or resolves to a private or loopback address.
event_types.*Not one of link.created, link.clicked, pixel.fired.

One endpoint does not use the envelope. When POST /v1/domains rejects a hostname at the domain-parsing stage, it answers 406 Not Acceptable with a bare message key rather than the error object:

{ "message": "not-a-domain is not a valid domain name. Ensure your domain name is valid" }

Handle a 406 from that endpoint as a validation failure. Everything else in the API uses the standard envelope.

Rate limits

Requests are limited to 60 per minute, counted per API key.

Two keys on the same account get independent budgets, and keys are never throttled by unrelated traffic sharing an egress IP address — which matters if you call the API from a NAT gateway, a CI runner, or a serverless platform.

Reading your budget

Successful responses carry the current state of your bucket:

HeaderDescription
X-RateLimit-LimitRequests allowed per minute — 60
X-RateLimit-RemainingRequests left in the current window

When you exceed it

{ "error": { "code": "rate_limited", "message": "Too many requests." } }

The window is a rolling minute. Back off and retry — an exponential backoff starting around one second, with jitter, is a reasonable default.

Staying under the limit

  • Page in bulk. Use per_page=100 on list endpoints rather than making seven times as many calls at the default of 15.
  • Prefer /stats over /clicks when you only need counts. One aggregated call replaces paging through thousands of individual clicks.
  • Use webhooks instead of polling. Subscribing to link.clicked removes the need to poll for new clicks at all, which is what most rate-limit pressure turns out to be.
  • Serialise your workers. If several processes share one key, they share one 60/minute budget. Give each its own key, or put them behind a single rate-limited queue.
Last updated on August 28, 2026
Webhooks
Link Squeeze LogoLink Squeeze

Branded short links with built-in retargeting pixels for performance marketers.

Product
  • Pricing
  • Sign up
  • Log in
  • Use cases
  • Docs
  • API
  • Blog
Free tools
  • Link shortener
  • UTM builder
  • Link Preview Generator
  • All tools
Legal
  • Privacy policy
  • Terms of service
  • Support
Compare
  • Bitly
  • Rebrandly
  • Short.io
  • Replug
  • Dub
© 2026 Link Squeeze. All rights reserved.Made for marketers who measure everything.