List leads
List jobs matching a campaign's filters as stable, biddable leads.
GET /v1/campaigns/:campaignId/leadsReturns jobs from the Upwork job pool that match the campaign's saved filters, newest first. Each returned job is materialized into a persisted lead with a stable id, so you can bid on or reject any item immediately. This works on brand-new campaigns too; you do not need to wait for webhooks to start integrating.
The list is the same in every campaign mode: it returns filter matches, unscored. Leads materialized here have not been through Lancer's suitability AI, so suitabilityRating and suitabilityReason are null on them even for Standard and External proposal campaigns (a lead the pipeline has already scored keeps its values).
Beyond driving bids, this endpoint is the easiest way to test your decision logic and prompts against a real batch of jobs: pull a page, run your suitability scoring or proposal generation over it, and iterate without waiting for new webhooks to fire.
Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
offset | integer | 0 | Number of items to skip. |
limit | integer | 100 | Page size, 1 to 100. |
Example
curl "https://api.lancer.app/v1/campaigns/CAMPAIGN_ID/leads?limit=10" \
-H "Authorization: Bearer lk_..."Response
{
// The page of leads, newest first (ordered by createdAt desc). Each item
// is exactly the "lead" object you would receive on a job.new webhook,
// plus bidUrl / rejectUrl for convenience.
"data": [
{
"id": "lead_abc123",
"title": "Next.js developer for dashboard rebuild",
"description": "We are looking for...",
"jobUrl": "https://www.upwork.com/jobs/~021234567890",
"createdAt": 1754450000000,
"datetime": 1754449000000,
"category": "Web Development",
"skills": [{ "name": "Next.js" }, { "name": "TypeScript" }],
"questions": [
"Describe your recent experience with similar projects"
],
"metadata": {
"experienceLevel": "Expert",
"paymentType": "Hourly",
"hourlyRate": { "min": 30, "max": 60 },
"fixedPrice": null,
"duration": "3 to 6 months",
"hours": "More than 30 hrs/week"
},
"clientInfo": {
"isPaymentVerified": true,
"rating": 4.9,
"numberOfReviews": 41,
"country": "United States",
"totalSpent": 120000,
"numberOfHires": 38,
"hireRate": 71,
"memberSince": "2019-03-12"
},
"campaignId": "cmp_123",
"campaignName": "US SaaS clients",
"organizationId": "org_123",
// Lancer's suitability verdict. null for leads materialized by this
// endpoint (they are unscored); populated only once the campaign's
// pipeline has scored the lead.
"suitabilityRating": null,
"suitabilityReason": null,
"bidUrl": "https://api.lancer.app/v1/campaigns/cmp_123/leads/lead_abc123/bid",
"rejectUrl": "https://api.lancer.app/v1/campaigns/cmp_123/leads/lead_abc123/reject"
}
],
// Total number of matching leads in this campaign across all pages.
"total": 342,
// Echo of the offset/limit you sent (or the defaults, if you did not).
"offset": 0,
"limit": 10,
// true if offset + data.length < total. Stop paginating when this is false.
"hasMore": true
}The lead object is the same shape delivered in webhook payloads, plus ready-to-call bidUrl and rejectUrl. The full field list is documented there.
Timestamps
All timestamps inside lead objects are UNIX milliseconds.
Errors
| Status | When |
|---|---|
401 | Missing or invalid key, or the subscription is inactive. |
404 | Campaign not found (wrong id, or the campaign belongs to another organization). |