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.
/job-detail Jobs https://api.fetchlayer.dev/upwork/job-detail 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."
}