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 minute, it resets on the minute, and it is measured per key, not per workspace and not per IP address. Two keys in the same workspace each get the full budget.

That is also why plans cap how many live keys a workspace may hold: the cap is what keeps the throughput of a plan meaningful. Within it, splitting your integrations across separate keys is a feature, not a workaround: one busy job then cannot starve another.

Reading your budget

Every authenticated response carries the budget, so you never have to discover the ceiling by hitting it:

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 minute for this key.
  • X-RateLimit-Remaining: how many are left in the current minute.

The headers are on error responses too, including the 429 itself. The one case with no headers is a 401: authentication fails before there is a key to measure, so there is nothing to report.

Going over

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

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

Nothing was created, so the request is safe to repeat. The window is a fixed minute rather than a rolling one, which makes the recovery simple: wait for the clock to tick over to the next minute and send it again. Exponential backoff buys nothing here, because the budget does not refill gradually, it refills all at once.

# The window is a fixed minute, not a rolling one, so there is no value in
# retrying sooner than the next minute, and none in exponential backoff.
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

    # Sleep to the top of the next minute, when the budget refills at once.
    sleep $(( 61 - $(date +%S) ))
  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.
  • Listing is cheap, and 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 a separate thing. Rate limits are about requests per minute; the monthly link allowance is about links created. Running out of one tells you nothing about the other.