UpHunt API

Rate Limits

Request quotas, burst limits, and how to handle 429 responses.

The UpHunt API enforces per-key rate limits to protect Upwork integrity and keep the platform responsive for everyone.

Limits by endpoint

EndpointLimitNotes
POST /apply60/min, 1,000/dayOne credit burned only on success.
GET /status300/minPoll freely — cheap and idempotent.
GET /applied-jobs60/minCache results on your side where possible.
POST /generate-proposal30/min, 500/dayHigh reasoning effort counts the same.
GET /external/freelancers30/minCache for at least 5 minutes.
GET /api/v1/jobs/:ciphertext10,000/billing periodCounts against the shared API key quota.
GET /api/v1/clients/:companyId/jobs10,000/billing periodCounts against the shared API key quota.

Limits are per API key. If you run multiple environments (staging / production), generate a separate key per environment so their quotas don't share.

Monthly quota

The /api/v1/* endpoints share a single monthly quota of 10,000 requests per user per billing period. The window mirrors your subscription:

  • Active subscribers: the period ends at your next billing date and rewinds 30 days for the start.
  • No active subscription: calendar-month UTC fallback, resets on the 1st.

Every response carries X-RateLimit-Limit / Remaining / Reset so callers can self-pace. When the quota is consumed, requests return 429 with:

{
  "error": "rate_limit_exceeded",
  "message": "Monthly request limit of 10000 reached. Quota resets 2026-06-09T00:00:00.000Z.",
  "limit": 10000,
  "used": 10001,
  "resetsAt": "2026-06-09T00:00:00.000Z"
}

Response headers

Every response includes three headers you can use to pace yourself:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1732204800
  • X-RateLimit-Limit — the ceiling for the current window.
  • X-RateLimit-Remaining — requests left in the window.
  • X-RateLimit-Reset — Unix seconds when the window rolls over.

When you hit 0 remaining, subsequent requests return 429 with a Retry-After header (seconds).

Handling 429

Blindly retrying on 429 will keep you rate-limited. Always respect Retry-After or back off exponentially with jitter.

Node.js — token-bucket-lite
let tokens = 60
let nextRefill = Date.now() + 60_000
 
async function apply(body: unknown) {
  if (tokens <= 0) {
    await new Promise(r => setTimeout(r, Math.max(nextRefill - Date.now(), 0)))
    tokens = 60
    nextRefill = Date.now() + 60_000
  }
  tokens--
 
  const res = await fetch('https://uphunt.io/api/auto-apply-v2/apply', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', 'x-api-key': process.env.UPHUNT_API_KEY! },
    body: JSON.stringify(body),
  })
 
  if (res.status === 429) {
    const retryAfter = Number(res.headers.get('retry-after')) || 30
    await new Promise(r => setTimeout(r, retryAfter * 1000))
    return apply(body)
  }
 
  return res.json()
}

Requesting higher limits

Shipping high-volume agency traffic? Contact support from the Dashboard — we can raise limits on a case-by-case basis for verified accounts.

On this page