Urlicer API v1

Rate limits

How many requests a key may make, how to read the budget, and what to do when you run out.

Every request is counted against the key that made it. The budget is a whole number of requests per 60-second window, measured per key. It is not shared across a workspace and not tied to an IP address, so two keys in the same workspace each get the full budget.

The window starts with the first request a key makes and lasts 60 seconds from then. It does not line up with the clock minute. When it ends, the whole budget is available again, and the next request starts a new window.

Because the budget is per key, plans also cap how many live keys a workspace may hold. Within that cap, give each integration its own key, so one busy job cannot use up the budget another one needs.

Reading your budget

Every authenticated response carries the budget, so you can slow down before you reach the ceiling:

HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
Content-Type: application/json; charset=utf-8
  • X-RateLimit-Limit: requests allowed per 60-second window for this key.
  • X-RateLimit-Remaining: how many requests are left in the current window.

The headers are on error responses too, including the 429 itself. Two responses have no headers, because they are sent before the limiter runs: a 401, where there is no key to measure, and a 403 feature error for ApiAccess, where the workspace's plan does not include the API. A plan with no rate limit sends no headers either.

Going over

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 0
Content-Type: application/json; charset=utf-8

{"error": "rate_limit", "limit": 30}

Nothing was created, so the request is safe to repeat. The body carries the limit and nothing else: there is no reset time and no Retry-After header. Since the window lasts 60 seconds from the key's first request in it, waiting 60 seconds after a 429 is always enough. Exponential backoff does not help here, because the budget comes back all at once when the window ends.

# The window lasts 60 seconds from the key's first request in it, and the 429
# carries no reset time, so wait a full 60 seconds before trying again.
call_with_retry() {
  for attempt in 1 2 3 4 5; do
    status=$(curl -s -o response.json -w '%{http_code}' "$@")

    if [ "$status" != "429" ]; then
      cat response.json
      return 0
    fi

    # The window has ended by the time this returns, and the budget is full again.
    sleep 60
  done

  echo "Still rate limited after 5 attempts." >&2
  return 1
}

The numbers

Read live from our plan catalog, so this table is what is actually enforced right now:

Plan API requests Live keys Links per month
Free 30 / minute 1 500
Pro 120 / minute 3 5000
Team 600 / minute 10 50000
Business 3000 / minute 25 500000

Spending the budget well

  • A bulk create is one request. Creating ten thousand links through the bulk endpoint costs a single request against the budget; creating them one at a time costs ten thousand. If you are importing, import in bulk.
  • Pages hold up to 100 links. Reading a whole workspace with size=100 costs a hundredth of what reading it one link at a time would.
  • Cache what does not change. A link’s short URL never changes after it is created, so there is no reason to fetch it twice.
  • The link allowance is counted separately. Rate limits count requests per window; the monthly link allowance counts links created. Having requests left does not mean you have links left, and the other way round.