Upwork icon

Upwork data API: jobs, proposals, Connects and contracts

Upwork Global Inc. · Jobs & Careers

Upwork's Android client signs in with an OAuth2 AccessToken, then exchanges a scoped session at /v1/auth/scoped-token. Find Work cards load from /v1/feeds/best-match and /v1/feeds/most-recent (ciphertext, hourlyBudget, skills.prefLabel); opening a card hydrates /v1/jobs/{jobId}/posting with the client's public company stats (paymentVerified, totalCharges).

Membership & Connects reads /v1/connects/wallet (pibStatus.availableConnects). Proposals page at /v1/proposals (applicationUID, chargeRate, connectsBid) and interview invitations at /v1/invitations. Inbox rooms are /v1/inbox/rooms, the Alerts bell is /v1/alerts/{userId}, and a contract room hydrates hourly charge_rate plus combinedTotalEarnings from /v1/contracts/{contractId}. Paths shown are an illustrative model of the data, not a published developer API.

Upwork (package com.upwork.android.apps.main, app version 2.14.0) is Upwork Global's Android client for both freelancers and clients: Find Work job cards, proposals and interview invitations, Connects / Proposal Invitation Bonus, contract rooms, messages and talent search. This page documents an illustrative data surface modelled on those screens. Sign-in yields an OAuth2 AccessToken (accessToken, refreshToken, expiresInSecs, tenantId) that /v1/auth/scoped-token upgrades for data calls. Find Work cards come from /v1/feeds/best-match and /v1/feeds/most-recent (ciphertext, hourlyBudget, skills.prefLabel); a job-detail client card loads at /v1/jobs/{jobId}/posting (paymentVerified, totalCharges). The Connects wallet is /v1/connects/wallet (pibStatus.availableConnects); proposals page at /v1/proposals (applicationUID, chargeRate, connectsBid) and invitations at /v1/invitations. Inbox, alerts and contracts live at /v1/inbox/rooms, /v1/alerts/{userId} and /v1/contracts/{contractId}. Calls carry the Bearer AccessToken, X-Upwork-Authentication and X-Upwork-API-TenantId.

Screenshots

  • Upwork screenshot 1
  • Upwork screenshot 2
  • Upwork screenshot 3
  • Upwork screenshot 4
  • Upwork screenshot 5
  • Upwork screenshot 6

API surface

  • Issue scoped session token

    POST /v1/auth/scoped-token osint

    Exchanges 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the post-login handshake before Find Work hydrates
    • token fields match the session the rest of the screens reuse
  • Best-match job recommendations feed

    POST /v1/feeds/best-match opendata

    Powers 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
      }
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Find Work Best Match tab cards
    • budget range, skill chips and client spend/verified badge on each card
  • Most-recent job recommendations feed

    POST /v1/feeds/most-recent opendata

    Loads 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
      }
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Find Work Most Recent tab
    • published time and budget range shown on each recency-sorted card
  • Hydrate marketplace job posting

    GET /v1/jobs/{jobId}/posting opendata

    Hydrates 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
          }
        }
      }
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the job-detail client card
    • payment-verified badge and spend/hire stats on that card
  • Read Connects / invitation-bonus balance

    GET /v1/connects/wallet openfinance

    Returns 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
          }
        }
      }
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Membership & Connects wallet tile
    • availableConnects and per-bid currentPrice shown on that tile
  • List proposals by type

    POST /v1/proposals opendata

    Pages 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Proposals tab's Active / Submitted / Archived pivots
    • rate, Connects spent and cover-letter preview on each row
  • List interview invitations

    POST /v1/invitations opendata

    Pages 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Invitations tab rows
    • invitation letter preview and pending status chip on each row
  • List saved jobs

    GET /v1/saved-jobs opendata

    Returns 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
              }
            }
          }
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Saved jobs list
    • follow toggle, budget range and verified-client badge on each saved row
  • Recommended freelancers (talent search)

    GET /v1/talent/recommended osint

    Returns 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the recommended-talent strip on a job
    • hourly rate, occupation title and portrait on each card
  • List message rooms (simplified)

    GET /v1/inbox/rooms osint

    Returns 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Messages tab conversation list
    • unread badge counts on each room row
  • List room stories (simplified)

    GET /v1/inbox/rooms/{roomId}/stories osint

    Pages 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the in-room message thread after tapping a conversation
  • List user notifications

    GET /v1/alerts/{userId} osint

    Pages 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the Alerts bell dropdown
    • unread flag and title on each alert row
  • Read organization notification counts

    GET /v1/alerts/org-counts osint

    Returns 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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
            }
          ]
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the org-switcher badge counts on Alerts and Messages
  • Fetch contract details

    GET /v1/contracts/{contractId} openfinance

    Loads 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
      }
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the contract room header and earnings strip
    • hourly rate, weekly cap and lifetime earnings on that screen
  • List client contract offers

    POST /v1/hires/offers openfinance

    Pages 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

    Illustrative example reconstructed from the app's interface — not a live capture.

    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"
              }
            }
          }
        }
      ]
    }

    Derived from the app's interface; endpoint details are illustrative, not a live capture.

    • reconstructed from the client's Hires / Offers list
    • offer state chip, type and contract id on each row

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.

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

Get a quote