Upwork data API: jobs, proposals, Connects and contracts
Upwork, published by Upwork Global Inc. (part of Nasdaq-listed Upwork Inc.), is the Android app of the freelance marketplace born from the 2013 merger of Elance and oDesk and renamed Upwork in 2015, and one account runs both sides of a hire. Freelancers browse a Find Work feed split into Best Matches and Most Recent tabs, save jobs for later, open job details that show the client's payment-verification badge, location and hiring history, and bid with proposals paid for in Connects, the platform's bidding credits managed on the Membership & Connects screen. An Invitations tab collects invitations to interview, the Proposals tab tracks active, submitted and archived applications, and won work moves into contract rooms covering hourly terms with weekly limits or fixed-price terms, backed by earnings reports. Clients use the same app to browse recommended talent, send offers from the Hires screen and follow up in the Messages tab, while an Alerts bell flags job activity, interview invitations and new messages across every organization a user belongs to. Its audience is concentrated in the US, India and Pakistan, where businesses and independent professionals meet over hourly and fixed-price work, putting it in direct competition with Fiverr and Freelancer.com for remote independent talent.
Connects-priced proposals sit at the center of the record: each application carries its applicationUID, the openingUID of the job it targets, the freelancer's quoted chargeRate and the connectsBid spent to submit it, set against the wallet's availableConnects, currentPrice and consumedConnectFraction. Upstream, job cards hold hourlyBudget min/max ranges, prefLabel skill tags and a connectPrice, and the client's public company card adds paymentVerified, totalContracts, totalJobsWithHires and lifetime totalCharges. Interview invitations link a jobPostingUid to its contractorUid and clientUid; contracts, keyed by referenceContractRid, settle into charge_rate, weekly_limit and combinedTotalEarnings; on the hiring side, recommended-talent cards carry hourlyRateAmount and occupationTitle, and sent offers record their state and the freelancer's countryName. Message rooms and alerts round it out with unreadMessageCount and per-organization counters.
Staffing CRMs join proposals and interview invitations to client spend in totalCharges to see which clients actually hire; freelancer finance bots forecast weekly Connects burn from connectsBid and wallet pricing before the membership renews; agency ledgers reconcile contract rates and combinedTotalEarnings against the offers a client has sent; and hiring desks shortlist recommended talent by rate, then pick up any conversation already open in a message room. openData Studio turns Upwork's jobs, proposals, Connects and contracts into callable open data.
Screenshots
API surface
The endpoints and request/response examples below are reconstructed from the app's interface — illustrative, not a live capture.
Issue scoped session token
POST
/v1/auth/scoped-tokenosintExchanges the mobile session for a scoped token (accessToken, refreshToken, expiresInSecs, tenantId) required by feed, proposal and room calls that the cookie-exchange path otherwise issues.
Auth: Signed-in AccessToken plus session cookies. The server rejects a plain mobile Bearer when the scoped session-exchange is missing.
- accessToken
- refreshToken
- expiresInSecs
- tenantId
- grant_type
- scope
POST /v1/auth/scoped-token HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "grant_type": "client_credentials", "scope": "workspace" }{ "accessToken": "eyJhbGciOiJSUzI1NiJ9...", "refreshToken": "rt_7c2e91ab", "expiresInSecs": 3600, "tenantId": "1513535512629587969" }Best-match job recommendations feed
POST
/v1/feeds/best-matchopendataPowers Find Work best-match cards: uid, ciphertext, title, hourlyBudget min/max, skills.prefLabel, connectPrice and the client's totalSpent / paymentVerificationStatus for the signed-in freelancer.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- results
- uid
- title
- ciphertext
- description
- type
- recno
- hourlyBudget
- min
- max
- skills
- prefLabel
- connectPrice
- client
- totalHires
- totalSpent
- paymentVerificationStatus
- publishedOn
- paging
- total
- count
POST /v1/feeds/best-match HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "limit": 10, "offset": 0 }{ "results": [ { "uid": "1784869486235123712", "title": "Staff Product Manager", "ciphertext": "~015c7cd346853e24fd", "description": "Own the freelancer home feed.", "type": "HOURLY", "recno": 4123456789, "connectPrice": 16, "hourlyBudget": { "type": "RANGE", "min": 60, "max": 90 }, "skills": [ { "id": "1017484851352698921", "prefLabel": "Product Management" } ], "client": { "totalHires": 12, "totalSpent": 71401.96, "paymentVerificationStatus": "VERIFIED" }, "publishedOn": "2026-09-20T12:00:00.000Z" } ], "paging": { "total": 248, "count": 10 } }Most-recent job recommendations feed
POST
/v1/feeds/most-recentopendataLoads the Most Recent Find Work tab (id, ciphertext, publishedDateTime, title, type, recno, hourlyBudget) used when the freelancer switches off best-match ranking.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- results
- id
- uid
- ciphertext
- publishedOn
- title
- type
- recno
- hourlyBudget
- attrs
- skills
- prefLabel
- paging
POST /v1/feeds/most-recent HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "limit": 10 }{ "results": [ { "id": "1784869486235123712", "uid": "1784869486235123712", "ciphertext": "~015c7cd346853e24fd", "publishedOn": "2026-09-21T08:15:00.000Z", "title": "Android contractor for wallet SDK", "type": "FIXED", "recno": 4123456790, "hourlyBudget": { "type": "RANGE", "min": 45, "max": 70 }, "attrs": [ { "id": "1017484851352698921", "prefLabel": "Android" } ] } ], "paging": { "total": 248, "count": 10 } }Hydrate marketplace job posting
GET
/v1/jobs/{jobId}/postingopendataHydrates a job-detail screen with the client's public company card: country, paymentVerified, totalContracts, totalJobsWithHires and totalCharges.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- id
- clientCompanyPublic
- country
- twoLetterAbbreviation
- timezone
- memberSinceDateTime
- paymentVerification
- paymentVerified
- workHistoryStats
- totalContracts
- totalJobsWithHires
- totalCharges
- currency
- displayValue
GET /v1/jobs/1802671287393773389/posting HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "id": "1802671287393773389", "clientCompanyPublic": { "id": "1009763084", "country": { "id": "922824128549781504", "name": "United States", "twoLetterAbbreviation": "US" }, "timezone": "America/Los_Angeles", "memberSinceDateTime": "2022-08-18T00:00:00.000Z", "paymentVerification": { "paymentVerified": true }, "workHistoryStats": { "totalContracts": 12, "totalJobsWithHires": 9, "totalCharges": { "currency": "USD", "displayValue": "71401.96" } } } }Read Connects / invitation-bonus balance
GET
/v1/connects/walletopenfinanceReturns the freelancer's Proposal Invitation Bonus / Connects wallet (pibStatus.availableConnects, currentPrice, maxPrice, consumedConnectFraction) used by Membership & Connects.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- pibStatus
- personId
- active
- currentPrice
- maxPrice
- consumedConnectFraction
- availableConnects
- connectsBalance
- connectsBalanceFree
- connectsBalancePaid
- visibilityLevel
GET /v1/connects/wallet HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "pibStatus": { "personId": "1513535512629587968", "active": true, "currentPrice": 8, "maxPrice": 37, "consumedConnectFraction": 0.01, "availableConnects": 163 }, "user": { "freelancerProfile": { "userPreferences": { "visibilityLevel": 1 } } } }List proposals by type
POST
/v1/proposalsopendataPages the Proposals tab (active/submitted/archived): applicationUID, openingUID, chargeRate, connectsBid, coverLetter and occupationTitle for each application.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- paging
- total
- offset
- applications
- applicationUID
- vendorUID
- openingUID
- title
- status
- coverLetter
- terms
- chargeRate
- amount
- currency
- connectsBid
- duration
- occupationTitle
POST /v1/proposals HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "type": "ACTIVE", "limit": 10 }{ "paging": { "total": 42, "offset": "eyJBY3RpdmUiOiIxMCJ9", "count": 10 }, "applications": [ { "applicationUID": "1784936978461474817", "vendorUID": "1513535512629587968", "openingUID": "1784869486235123712", "title": "Lead Paradigm Representative", "status": 7, "coverLetter": "I can start this week.", "terms": { "chargeRate": { "amount": "85", "currency": "USD" }, "connectsBid": 17, "duration": 0 }, "occupationTitle": "Product Management" } ] }List interview invitations
POST
/v1/invitationsopendataPages Invitation to interview rows (uid, jobPostingUid, status, invitationLetter, contractorUid, clientUid) that populate the Invitations tab.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- totalCount
- count
- invitations
- uid
- jobPostingUid
- title
- status
- statusId
- invitationLetter
- contractorUid
- clientUid
- ctime
POST /v1/invitations HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "status": "PENDING", "pagination": { "offset": 0, "count": 10 } }{ "totalCount": 826, "count": 10, "invitations": [ { "uid": "1784872122316197888", "jobPostingUid": "1784872118568779776", "title": "Legacy Division Technician", "status": "Pending", "statusId": 0, "invitationLetter": "We would like to interview you this week.", "contractorUid": "1441159363691524096", "clientUid": "1783553054015062016", "ctime": "2026-04-29T09:07:29.000Z" } ] }List saved jobs
GET
/v1/saved-jobsopendataReturns Saved jobs with followed/jobRid plus the posting's title, hourlyBudgetMin/Max and the client's paymentVerified / totalCharges card.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- personSavedJobs
- followed
- jobRid
- job
- id
- content
- title
- description
- contractTerms
- hourlyContractTerms
- hourlyBudgetType
- hourlyBudgetMin
- hourlyBudgetMax
- clientCompanyPublic
- paymentVerified
- workHistoryStats
- totalCharges
- rawValue
GET /v1/saved-jobs?limit=20&offset=0&followed=true HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "personSavedJobs": [ { "followed": true, "jobRid": "4123456789", "job": { "id": "1784869486235123712", "content": { "title": "Staff Product Manager", "description": "Own the freelancer home feed." }, "contractTerms": { "hourlyContractTerms": { "hourlyBudgetType": "RANGE", "hourlyBudgetMin": 60, "hourlyBudgetMax": 90 } }, "clientCompanyPublic": { "paymentVerification": { "paymentVerified": true }, "workHistoryStats": { "totalCharges": { "rawValue": 71401.96, "currency": "USD", "displayValue": "71401.96" }, "totalContracts": 12 } } } } ] }Recommended freelancers (talent search)
GET
/v1/talent/recommendedosintReturns ranked freelancer cards (id, ciphertext, hourlyRateAmount, occupationTitle, portraitUrl) that the client Talent search / recommended-talent strip hydrates.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- freelancers
- id
- name
- photoUrl
- profileUrl
- ciphertext
- hourlyRateAmount
- occupationTitle
- portraitUrl
GET /v1/talent/recommended?openingId=1784869486235123712&limit=10 HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "freelancers": [ { "id": "1939570823820670781", "name": "Ruthie L.", "photoUrl": "https://cdn.example.com/portraits/c1example", "profileUrl": "/freelancers/~01a4efc20a2cd41f48", "ciphertext": "~01a4efc20a2cd41f48", "hourlyRateAmount": 75, "occupationTitle": "Product Manager", "portraitUrl": "https://cdn.example.com/portraits/c1example" } ] }List message rooms (simplified)
GET
/v1/inbox/roomsosintReturns the simplified room list (id, unreadStoriesCount, unreadMessageCount) that seeds the Messages tab before a full room payload is fetched.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- rooms
- id
- roomId
- title
- unreadStoriesCount
- unreadMessageCount
- unreadStories
- userId
GET /v1/inbox/rooms?userId=1513535512629587968 HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "rooms": [ { "id": "room_5a82d4931d5f5aea3817fd0a14fcbbba", "roomId": "room_5a82d4931d5f5aea3817fd0a14fcbbba", "title": "Staff Product Manager", "unreadStoriesCount": 2, "unreadMessageCount": 2, "unreadStories": true, "userId": "1513535512629587968" } ] }List room stories (simplified)
GET
/v1/inbox/rooms/{roomId}/storiesosintPages the simplified story (message) list for one room after the Messages tab opens a conversation.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- stories
- storyId
- roomId
- unreadStoriesCount
GET /v1/inbox/rooms/room_5a82d4931d5f5aea3817fd0a14fcbbba/stories HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "stories": [ { "storyId": "story_9f3a1c", "roomId": "room_5a82d4931d5f5aea3817fd0a14fcbbba", "unreadStoriesCount": 1 } ] }List user notifications
GET
/v1/alerts/{userId}osintPages the Alerts bell: notificationId, unread flags and title for job activity, invitations and room events.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- notifications
- notificationId
- unread
- notification_unread
- title
- userId
GET /v1/alerts/1513535512629587968 HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "notifications": [ { "notificationId": "n_9f3a1c", "unread": true, "notification_unread": true, "title": "Invitation to interview", "userId": "1513535512629587968" } ] }Read organization notification counts
GET
/v1/alerts/org-countsosintReturns per-organization notification counters (organizations[].counters) that drive the Alerts and Messages badges.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- organizations
- id
- orgUid
- counters
- count
GET /v1/alerts/org-counts HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "organizations": [ { "id": "1513535512629587969", "orgUid": "1513535512629587969", "counters": [ { "id": "messages", "count": 2 } ] } ] }Fetch contract details
GET
/v1/contracts/{contractId}openfinanceLoads a Contract room: uid, referenceContractRid, hourly charge_rate and weekly_limit, plus combinedTotalEarnings used by Reports / earnings history.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- uid
- referenceContractRid
- state
- clientOrgUid
- clientTeamUid
- freelancerUid
- offerDetails
- title
- hourly
- charge_rate
- weekly_limit
- start_date
- fixedPrice
- combinedTotalEarnings
- currency
- displayValue
GET /v1/contracts/1669484061874712576 HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId>{ "uid": "1669484061874712576", "referenceContractRid": "20799532", "state": 1, "clientOrgUid": "1638232740248182785", "clientTeamUid": "1638232740248182785", "freelancerUid": "1663144681572958208", "offerDetails": { "title": "Staff Product Manager", "description": "Hourly product contract", "hourly": { "charge_rate": "85", "weekly_limit": "40", "start_date": "2026-09-17T00:00:00.000Z" }, "fixedPrice": null }, "combinedTotalEarnings": { "currency": "USD", "displayValue": "12840.00" } }List client contract offers
POST
/v1/hires/offersopenfinancePages the client's sent offers: offer id, title, state, type, contract.rid and freelancer.location.countryName for the Hires / Offers screens.
Auth: OAuth2 Bearer AccessToken issued after sign-in; X-Upwork-Authentication and X-Upwork-API-TenantId. GraphQL-style data calls additionally expect a scoped session-exchange token — a plain mobile Bearer is rejected when that scope is missing.
- totalCount
- edges
- node
- id
- startDateTime
- endDateTime
- title
- state
- type
- contract
- rid
- agency
- name
- freelancer
- location
- countryName
POST /v1/hires/offers HTTP/1.1 Authorization: Bearer <accessToken> X-Upwork-Authentication: <accessToken> X-Upwork-API-TenantId: <tenantId> Content-Type: application/json { "pagination": { "offset": 0, "count": 10 } }{ "totalCount": 3, "edges": [ { "node": { "id": "101694088", "startDateTime": "2026-09-17T00:00:00.000Z", "endDateTime": null, "title": "Staff Product Manager", "state": "ACCEPTED", "type": "HOURLY", "contract": { "rid": "20799532" }, "agency": { "name": "Northwind Studio" }, "freelancer": { "location": { "countryName": "United States" } } } } ] }
Data categories
- job postings
- proposals
- interview invitations
- Connects balance
- contracts
- client company stats
- message rooms
- notifications
- freelancer profiles
Where teams use this data
Freelance talent CRM from job + proposal fields
A staffing CRM can poll POST /v1/proposals and POST /v1/invitations for applicationUID, openingUID, chargeRate, connectsBid and invitation status, then join each row to GET /v1/jobs/{jobId}/posting (paymentVerified, totalCharges, country) so recruiters see which clients actually spend.
Connects wallet and bid-cost tracking
A freelancer finance bot can read pibStatus (availableConnects, currentPrice, maxPrice, consumedConnectFraction) on GET /v1/connects/wallet and the connectsBid on each submitted proposal to forecast weekly Connects burn before the Membership & Connects screen renews.
Contract rate and earnings reconciliation
An agency ledger can pull GET /v1/contracts/{contractId} for hourly charge_rate, weekly_limit and combinedTotalEarnings, keyed by uid / referenceContractRid, and cross-check those figures against POST /v1/hires/offers on the client's Hires screen.
Client-side recommended-talent shortlist
A hiring desk can call GET /v1/talent/recommended for ciphertext, hourlyRateAmount, occupationTitle and portraitUrl, then open the matching simplified room on GET /v1/inbox/rooms when a conversation already exists.
Frequently asked questions
How does the Upwork Android app authenticate data calls?
Sign-in yields an OAuth2 AccessToken (accessToken, refreshToken, expiresInSecs, tenantId). Subsequent calls send Authorization: Bearer plus X-Upwork-Authentication and X-Upwork-API-TenantId. Feed, proposal and room hosts additionally expect a scoped token from POST /v1/auth/scoped-token — a plain mobile Bearer is rejected when that scope is missing.
Which data powers Find Work job cards?
POST /v1/feeds/best-match and POST /v1/feeds/most-recent return uid, ciphertext, title, hourlyBudget and skills.prefLabel. Opening a card hydrates GET /v1/jobs/{jobId}/posting, including paymentVerified and workHistoryStats.totalCharges on the client's public company card.
Where is the Connects / invitation-bonus balance exposed?
The Membership & Connects screens read GET /v1/connects/wallet: pibStatus.availableConnects, currentPrice, maxPrice and consumedConnectFraction. Submitted proposals also carry terms.connectsBid.
Can I read contracts and message rooms, not just jobs?
Yes. GET /v1/contracts/{contractId} returns uid, hourly charge_rate, weekly_limit and combinedTotalEarnings. GET /v1/inbox/rooms lists rooms with unreadStoriesCount, and GET /v1/alerts/{userId} pages the Alerts bell.
Apps similar to Upwork
- Fiverr — Fiverr is an Israeli online marketplace where buyers purchase packaged freelance services (gigs) rather than posting an open job brief.
- Freelancer — Freelancer is an Australian marketplace where clients post jobs and contests, with a large share of users in India, the United States, the Philippines, Pakistan and the United Kingdom.
- Toptal — Toptal is a remote talent network that screens freelance developers and designers before matching them with companies.
- Guru — Guru.com is a Pittsburgh-based freelance marketplace that supports fixed-price, hourly, task-based and recurring contracts.
- PeoplePerHour — PeoplePerHour is a London-based freelance marketplace that connects clients with independent professionals.
- LinkedIn: Community & Network — LinkedIn is a professional network with job listings and an Open for Business profile so freelancers can be discovered by clients.
- 99designs — 99designs is a freelancer platform that connects graphic designers with clients through contests and direct hiring.
Topics
- Upwork data API
- Upwork job feed
- Upwork best-match jobs
- Upwork Connects wallet
- Upwork proposals list
- Upwork interview invitations
- Upwork contract earnings
- Upwork inbox rooms
- Upwork talent search
- Upwork AccessToken
Need this app's data API integrated?
We deliver scoped integrations for any named app — from USD 500 with source-code handoff, or hosted access billed per call. Tell us the data you need.
- NDA + SOW on every engagement
- Delivery in 3–7 days
- Payment only after acceptance
- Work scoped to authorized use