UpHunt API

How Auto-Apply Works

End-to-end walkthrough of the UpHunt auto-apply pipeline — from API call to terminal state.

UpHunt's auto-apply runs on a dedicated business developer — an Upwork account UpHunt manages — that lives inside your Upwork agency as a regular member. When you call /apply, the proposal is submitted through our business developer. Thanks to Upwork's Agency Plus feature, it goes out on behalf of your agency and the specific freelancer you pick.

This page walks through the pipeline end-to-end so you can design integrations that are resilient to every failure mode along the way. If you haven't set things up yet, start with Auto-Apply Setup.

The pipeline at a glance

You submit an application

Your request hits POST /api/auto-apply-v2/apply with a jobId, coverLetter, and optional proposal fields. We validate the payload, resolve the job (URL, ciphertext, or processed UUID all map to the same internal job), and check that your account has at least one auto-apply credit.

If any of that fails, you get a non-2xx response immediately and no credit is touched.

The job is queued

On success we insert a queue record (queueId), return it to you, and hand the job off to the worker pool. Your API call returns in under a second — everything after this happens asynchronously.

Credits are reserved, not spent. If the worker fails before Upwork accepts the proposal, the reservation is released and you keep the credit.

A worker picks it up

A background worker claims the queue record and has our business developer (already authenticated on Upwork) open the job page.

The profileId on your request decides which freelancer in your agency the proposal is sent on behalf of. Because Agency Plus lets any agency member submit proposals under the agency, you can pick any freelancer from your roster — their name, title, and rate show on the proposal. List available freelancers with GET /freelancers. If you omit profileId, we fall back to the business developer's default profile.

The proposal is filled

The worker opens the job page, detects hourly vs. fixed-price, and populates:

  • Cover letter — the coverLetter you sent, verbatim.
  • Bid amountproposal.hourlyRate or proposal.fixedBid. If omitted, we use the profile's default rate from the selected profileId.
  • Timelineproposal.timeline (defaults to what the job post suggests).
  • Boost Connectsproposal.boostBids additional Connects, if you opted in.
  • Screening questions — answered by GPT from your profile context when autoFillOtherQuestions: true. Otherwise left blank.

Upwork accepts or rejects

The worker clicks Send Proposal and waits for Upwork's response. One of three things happens:

  • Accepted — the proposal is submitted. Status transitions to applied, one credit is deducted, and a webhook fires if you've configured one.
  • Rejected by Upwork — eligibility, connects, or availability issue. Status transitions to a specific terminal state (see below). No credit deducted.
  • Worker exception — network error, page timeout, unexpected layout change. Status transitions to failed with an errorMessage. No credit deducted.

You observe the result

You observe the terminal state either by polling GET /status with the queueId, or by receiving a webhook on the application.applied or application.failed event.

The state machine

Every auto-apply job moves through exactly one of these terminal states:

StatusMeaning
processingWorker has the job but hasn't reached a terminal state yet
appliedProposal successfully submitted — credit deducted
failedWorker exception or unexpected error — credit refunded, see errorMessage
not_availableJob was closed/deleted before we could apply — credit refunded
not_eligibleUpwork rejected based on profile fit (location, skills, tier) — credit refunded
not_enough_connectsYour agency is out of Connects — credit refunded, top up on Upwork
logged_outBusiness developer session temporarily invalidated — credit refunded, UpHunt reconnects automatically

The only state that deducts a credit is applied. Every other terminal state releases the reservation.

How the proposal is actually submitted

Proposals are submitted through our business developer — an Upwork account UpHunt manages that's already a member of your agency. Because Agency Plus lets any agency member submit proposals on behalf of the agency (and any other freelancer in it), clients see the proposal exactly as they'd see one from a human agency member — sent on behalf of your agency and the freelancer you pick, not us.

Two practical details worth knowing:

  • Authentication: UpHunt keeps each business developer authenticated on Upwork in the background. If something ever breaks the authentication, the next apply briefly returns logged_out while we reconnect — automatically and without any action from you.
  • Rate shaping: applications from the same business developer are paced to match human behavior. Back-to-back /apply calls on the same agency add a few seconds of queue time; applications across different agencies run fully in parallel.

Latency budget

StatusMeaning
API response< 1s — just validation + enqueue
Worker pickupUsually < 5s, up to 30s under load
Proposal submission20–60s — dominated by Upwork-side latency
End-to-end p50~40s from /apply to terminal state
End-to-end p95~90s

Poll every 5 seconds for the first minute, then every 15 seconds thereafter. See the polling recipe for a ready-made implementation.

Webhooks vs. polling

Both work. Pick one:

  • Webhooks — push-based, no polling cost, ideal for automation. The delivery header X-UpHunt-Delivery lets you dedupe retries. See Webhooks.
  • Polling — pull-based, simpler to build, fine for interactive UIs where a user is already waiting.

Don't mix: if you use webhooks, stop polling once you receive the terminal event.

Next steps

On this page