# UpHunt API — Full Reference > Auto-apply for Upwork jobs, programmatically. Apply to any job by URL or ciphertext — no feed setup required. OpenAPI spec: https://uphunt.io/openapi.json ## Base URL https://uphunt.io ## How auto-apply works (setup model) UpHunt submits Upwork proposals through Upwork's **Agency Plus** feature. The setup, in one paragraph: UpHunt provides you with a dedicated "business developer" — an Upwork account UpHunt fully manages. You invite that business developer into your Upwork agency (the same way you'd invite any human freelancer), and the agency's Agency Plus subscription lets them submit proposals on behalf of your agency. When you call `/apply`, UpHunt submits the proposal through the business developer. Clients see the proposal submitted on behalf of your agency and the freelancer you pick. Requirements: - An Upwork agency with Upwork's **Agency Plus** subscription enabled. - UpHunt's business developer (assigned to you at signup) invited as a member of that agency. - An UpHunt account with auto-apply credits and an API key. **Submit on behalf of any freelancer in your agency.** Agency Plus lets any member of an agency submit proposals on behalf of any other member. So for each `/apply` call (or each auto-apply feed in the UpHunt dashboard), you pick which freelancer in your agency the proposal is sent as — they appear to the client as the applicant, with their own name, title, rate, and portfolio. Call `GET /api/auto-apply/external/freelancers` to list every freelancer in your agency with their available profiles (default + specializations), then pass the chosen `profileId` on the `/apply` call. If you omit `profileId`, we fall back to the business developer's default profile. **Upwork Terms of Service compliance.** Using UpHunt is fully within Upwork's TOS. The automation runs on UpHunt's business developer account — not on your agency-owner account or any personal Upwork account. Upwork's Agency Plus feature explicitly permits agency members to submit proposals on behalf of the agency, which is exactly what the business developer does. You never share your Upwork password with UpHunt and UpHunt never logs into your accounts. Full setup guide: https://uphunt.io/docs/auto-apply-setup Pipeline walkthrough: https://uphunt.io/docs/how-auto-apply-works --- ## Authentication All requests require an API key in the `x-api-key` header. Generate your key from Dashboard → Auto-Apply → API & Webhooks at https://uphunt.io/dashboard. x-api-key: sk_uphunt_abc123... Keep your key secret. Never expose it in client-side code. Regenerate immediately if compromised. --- ## POST /api/auto-apply-v2/apply Queue a proposal submission for an Upwork job. One credit is deducted on success. Request body: - `jobId` (string, required): Upwork job identifier. Accepts ciphertext (`~01abc...`), full URL (`https://www.upwork.com/jobs/~01...`), or processed job UUID. You can pass `ciphertext` directly from a webhook payload. - `coverLetter` (string, required): Proposal text. - `profileId` (string, optional): Freelancer profileId from /api/auto-apply/external/freelancers. Uses primary profile if omitted. - `proposal` (object, optional): - `hourlyRate` (number): USD hourly bid, hourly jobs only. - `fixedBid` (number): USD fixed bid, fixed-price jobs only. - `timeline` (string): "Less than 1 month" | "1 to 3 months" | "3 to 6 months" | "More than 6 months". - `boostBids` (integer): Extra Connects to boost proposal visibility. - `autoFillOtherQuestions` (boolean, optional): AI-answer additional screening questions. Default false. Example: curl -X POST https://uphunt.io/api/auto-apply-v2/apply \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "jobId": "~01abc123def456", "coverLetter": "Hi, I would love to help with this project...", "proposal": { "hourlyRate": 65, "timeline": "1 to 3 months" }, "profileId": "~01f2a3b4c5d6e7f8" }' Response: { "success": true, "queueId": "queue_abc123", "creditsRemaining": 42, "message": "Job queued for auto-apply. Credits will be deducted upon successful application." } Fixed-price jobs: if `proposal.fixedBid` is omitted, the bid defaults to the client's posted budget, falling back to $100. Hourly jobs: if `proposal.hourlyRate` is omitted, the rate defaults to the low end of the job's hourly range, falling back to $30/hr. --- ## GET /api/auto-apply-v2/status Poll the status of a submitted application. Provide either `queueId` or `jobId`. Query parameters: - `queueId` (string): Queue ID from the apply endpoint. - `jobId` (string): Upwork ciphertext (`~01abc...`) or processed job UUID. Example: curl "https://uphunt.io/api/auto-apply-v2/status?queueId=queue_abc123" \ -H "x-api-key: YOUR_API_KEY" Response: { "jobId": "~01abc123def456", "processedJobId": "uuid-here", "applicationStatus": "applied", "applicationStatusMessage": "Successfully applied via auto-apply", "queueId": "queue_abc123", "appliedAt": 1732204800000, "processedAt": 1732204790000, "errorMessage": null } `applicationStatus` values: - `processing` — currently being submitted. - `applied` — successfully applied. - `failed` — application failed, check `errorMessage`. - `not_available` — job no longer available on Upwork. - `not_eligible` — your agency profile does not meet the job requirements. - `not_enough_connects` — your agency does not have enough Upwork Connects (top up on Upwork). - `logged_out` — business developer session temporarily invalidated; UpHunt reconnects automatically, no action required. Retry after a short delay. --- ## GET /api/auto-apply-v2/applied-jobs Paginated list of jobs you have applied to (or attempted to apply to). Query parameters: - `limit` (integer): 1–200, default 50. - `offset` (integer): Default 0. - `status` (string, optional): Filter by `applicationStatus` value. Example: curl "https://uphunt.io/api/auto-apply-v2/applied-jobs?limit=10&status=applied" \ -H "x-api-key: YOUR_API_KEY" Response: { "jobs": [ { "jobId": "~01abc123def456", "processedJobId": "550e8400-e29b-41d4-a716-446655440000", "title": "React Developer needed", "url": "https://www.upwork.com/jobs/~01abc123def456", "platform": "upwork", "applicationStatus": "applied", "applicationStatusMessage": "Successfully applied via auto-apply", "matchingScore": 8, "queueId": "queue_abc123", "appliedAt": 1732204800000, "processedAt": 1732204790000, "errorMessage": null, "coverLetter": "Hi, I have 5+ years of experience with...", "createdAt": 1732204700000 } ], "total": 142, "limit": 10, "offset": 0 } --- ## POST /api/auto-apply-v2/generate-proposal Generate an AI cover letter. Pass `jobId` to look up a scraped job, or `jobTitle` + `jobDescription` to skip the lookup. Request body: - `jobId` (string): Ciphertext, URL, or processed job UUID. Required unless `jobTitle` + `jobDescription` are provided. - `jobTitle` (string): Job title. - `jobDescription` (string): Full job description. - `feedId` (string): Job listener ID whose custom prompt should be used. Falls back to generic prompt. - `reasoningEffort` (string): `low` | `medium` | `high`. Default `low`. Higher effort = better proposals, longer response time. Example (by jobId): curl -X POST https://uphunt.io/api/auto-apply-v2/generate-proposal \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "jobId": "~01abc123def456", "reasoningEffort": "medium" }' Example (by description): curl -X POST https://uphunt.io/api/auto-apply-v2/generate-proposal \ -H "Content-Type: application/json" \ -H "x-api-key: YOUR_API_KEY" \ -d '{ "jobTitle": "React Developer needed", "jobDescription": "We need an experienced React developer to...", "reasoningEffort": "high" }' Response: { "success": true, "proposal": "Dear hiring manager, I noticed your project requires..." } --- ## GET /api/auto-apply/external/freelancers List freelancers linked to your agency account with their profile options. Use the returned `profileId` when submitting proposals. Example: curl https://uphunt.io/api/auto-apply/external/freelancers \ -H "x-api-key: YOUR_API_KEY" Response: { "freelancers": [ { "id": "~01f2a3b4c5d6e7f8", "name": "John Doe", "avatarUrl": "https://www.upwork.com/profile-display/...", "baseHourlyRate": 50, "profiles": [ { "type": "default", "name": "Default Profile", "hourlyRate": 50, "currency": "USD", "profileId": "~01f2a3b4c5d6e7f8" }, { "type": "specialization", "name": "React Developer", "hourlyRate": 65, "currency": "USD", "profileId": "1234567890" } ] } ] } For `default` profiles, `profileId` is the freelancer's ciphertext. For `specialization` profiles, it's the service UID. --- ## Webhooks Configure a webhook URL on a job feed in your UpHunt dashboard. When a new job matches, UpHunt POSTs a job payload to your URL. Upwork payload (abbreviated): { "title": "React Developer needed", "description": "Full job description...", "ciphertext": "~01abc123def456", "jobUrl": "https://www.upwork.com/freelance-jobs/apply/~01abc123def456", "jobType": "HOURLY", "budget": { "fixedPrice": null, "hourlyMin": 30, "hourlyMax": 60 }, "contractorTier": "Expert", "client": { "location": { "city": "New York", "country": "United States" }, "stats": { "totalAssignments": 42, "score": 4.8 }, "paymentMethodVerified": true }, "skills": [ { "uid": "1110580482322976768", "name": "React", "highlighted": true } ], "publishTime": "2025-01-20T10:30:00.000Z", "matchingScore": 8, "jobListenerId": "listener_xyz", "jobListenerName": "My Feed", "platform": "upwork", "timestamp": 1706972400000 } LinkedIn payload (abbreviated): { "jobId": "4368632132", "title": "Data Engineer", "description": "Full job description...", "company": { "name": "Acme Corp", "logoUrl": "...", "profileUrl": "...", "slug": "acme-corp" }, "location": "United Kingdom", "seniorityLevel": "Mid-Senior level", "employmentType": "Contract", "industries": ["Technology"], "salaryRange": "£60.00/hr - £66.00/hr", "applicantCount": 25, "isEasyApply": true, "matchingScore": 8, "jobUrl": "https://www.linkedin.com/jobs/view/4368632132", "platform": "linkedin", "timestamp": 1706972400000 } Webhook endpoints must respond within 10 seconds. For longer processing, return 200 immediately and handle the apply call asynchronously. Use the webhook's `ciphertext` field as `jobId` when calling `/api/auto-apply-v2/apply`. --- ## Legacy endpoint `POST /api/auto-apply/external` — original v1 API. Accepts `jobId`, `coverLetter`, `applicationOptions` (`hourlyRate`, `fixedBid`, `timeline`, `boostBids`), `autoFillOtherQuestions`. Prefer /api/auto-apply-v2/apply for new integrations. --- ## Errors All errors return JSON with an `error` string field. - `400` Bad Request — check required fields (`jobId`, `coverLetter`) and formatting. - `401` Unauthorized — API key missing or invalid. - `402` Insufficient Credits — top up in dashboard. Response includes `creditsRemaining` and `needsCredits: true`. - `404` Not Found — jobId unknown or job not yet scraped. - `409` Conflict — already applied or currently processing. - `500` Server Error — retry after a moment. Example: { "error": "Insufficient auto-apply credits", "creditsRemaining": 0, "needsCredits": true }