Upwork Job Detail API — One Posting in Full
Documentation menu
FetchLayer FetchLayer API

Read one posting in full

Read one Upwork posting in full by its id or its URL — the fuller record behind a search result, with the exact posting time, the proposal count and the client's hire history. Give either `jobId` or `jobUrl`; an empty body is a 400 saying `"Provide either a jobId or a jobUrl."` **Coverage caveat, and the first thing to build around.** A search always returns the listing-level fields Upwork puts on its results page. The fuller record behind each posting — the exact posting time, the proposal count, the client's hire history — is **not always available**, and what is available varies by hour. When it is not, this route still answers **200**: the postings carry listing-level fields only, `enrichedJobs` comes back **lower than `jobs.length`** (0 when nothing expanded), every job is marked `enriched: false`, and `notes[]` says so in words — *"Full detail was not available for this request, so these postings carry listing-level fields only (see \"enriched\" on each job). Exact posting times, proposal counts and client hire history are omitted. Retrying often succeeds."* **Detect it with one comparison: `enrichedJobs` against `jobs.length`.** A thin row is a posting whose detail was not published to this request, not a posting with no proposals — reading it the second way silently corrupts any analysis built on it. Retrying often succeeds. **Here the same gap is a 503, not a thin 200**, because this route returns only that fuller record and has no listing-level rows to hand back instead: `{"error":"The upstream source is temporarily refusing automated requests. Please retry."}`. Four consecutive calls returned it during the window measured on 17 September 2026, while `/search-jobs` kept answering 200 throughout. Treat this route as best-effort: retry it, and fall back to the listing-level fields from a search when you need breadth rather than depth. Billing: 1 credit per page fetched, reported as `pagesFetched`. A 503 fetched no page.

POST /job-detail Jobs
https://api.fetchlayer.dev/upwork/job-detail
OpenAPI JSON

Headers

Authorization string required

Bearer token — your FetchLayer API key.

Parameters

jobId string optional

The posting's id, as returned on a search result's `id` or `uid`. Either this or `jobUrl` must be given.

jobUrl string optional

The posting's full Upwork address, as returned on a search result's `url`. Either this or `jobId` must be given.

format string optional

Envelope format: json or markdown. (default: json)

Response Fields

job-detail

Read one posting in full

transport string

The source. "upwork-jobs".

notes string[]

Caveats you can act on. The one that matters here says per-posting detail was not available and the postings carry listing-level fields only.

pagesFetched number

Pages read, and what the request is billed at: 1 credit per page. A search of one results page is 1 whether it returns 3 postings or 50.

job object

The posting, in the same shape as a `jobs[]` entry from /search-jobs — with `enriched: true` and the fuller fields filled in.

Request

curl -X POST "https://api.fetchlayer.dev/upwork/job-detail" \
  -H "Authorization: Bearer ss-your-key" \
  -H "Content-Type: application/json" \
  -d '{"jobUrl":"https://www.upwork.com/jobs/Digital-Skills-Teacher_~022100291793583666591/"}'

Response

{
  "error": "The upstream source is temporarily refusing automated requests. Please retry."
}