Puesto Labs
IntroductionAuthenticationPagination & sortingSyncing with the changes feedErrorsRate limitsBuilding with AI

Here you can add a description about your company or product

© Copyright 2026 Puesto Labs. All Rights Reserved.

About
  • Blog
  • Contact
Product
  • Documentation
  • llms.txt
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
Puesto Docs

Resolve a job by URL

GET
/api/v1/jobs/resolve

Look a job up by the URL of its posting: paste the link a candidate applied through and get back the SAME payload as Get a job by id — full company record, both description renderings, and the resolved locations. Built for application trackers, which hold a URL and need the catalog record behind it.

The URL is parsed server-side into the posting identity the applicant tracking system assigned it, and that identity is looked up. Pass the URL as the candidate sees it: tracking parameters (?gh_src=, utm_*), locale segments (/en-US/), a trailing /apply step and grnh.se short links are all handled. Only the url parameter is read; nothing else varies the result.

Closed jobs resolve. A posting taken down years ago answers exactly like a live one — the response is the ordinary job payload with closed_at set. That is the point of the endpoint for a tracker: an application made months ago still resolves. The only lookback limit is when we first indexed the board (a job closed before that never existed for us).

Provisional (live-fetched) results. A URL we can parse but have not indexed yet may be fetched live from the applicant tracking system and returned on the spot. A provisional result is the SAME 200 JobDetail shape, with one difference: id, source_id and company_id are null. The posting is not (yet) a catalog record, so it has no stable id to fetch later by Get a job by id — but everything else (title, company name, both description renderings, resolved locations, salary, level) is populated just like a catalog hit. Treat a null id as "resolved live, not yet catalogued"; render it, but do not store it as a catalog id or expect /jobs/{id} to return it.

The two 404s. A miss is never an error on your side, so both cases return 404 with a stable detail. Match on the detail PREFIX (the sentences below are stable; treat anything after them as free text):

detailMeaningWhat to do
Unsupported job board URL.The URL is not a job posting URL we can parse — an aggregator link, a bespoke careers site, or a board page rather than a posting.Stop retrying it. This will not start working.
No job matched the provided URL.The URL parsed fine, but nothing in the catalog matches it.Treat as "not indexed yet" and retry later if it matters.

The second answer is deliberately ONE answer: "we do not index that employer" and "we index that employer but not that posting" are byte-identical, and no field distinguishes them. Do not build logic that tries to. It is worth retrying because coverage grows: a posting less than a day old, or an employer we have just started indexing, can resolve on a later call for the same URL — every miss also feeds our board-discovery queue.

A malformed request — a missing url, a non-http(s) URL, or one over 2048 characters — is a 400 with the usual validation problem body, not a 404.

Rate limit. This endpoint draws on the same shared per-account budget as every other one (see Rate limits); it has no separate allowance. One lookup per tracked application, resolved once and cached on your side, sits comfortably inside it — resolving a whole backlog in a tight loop does not.

Authorization

bearerAuth
AuthorizationBearer <token>

Provide your secret API key (prefixed sk_) as a bearer token: Authorization: Bearer sk_....

In: header

Query Parameters

url*string

The job posting URL to resolve, exactly as it appears in the browser. Must be an absolute http/https URL of at most 2048 characters. Tracking parameters, locale segments and /apply suffixes are tolerated; grnh.se short links are followed.

Length1 <= length <= 2048

Response Body

application/json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

application/problem+json

curl -X GET "https://example.com/api/v1/jobs/resolve?url=string"
{  "id": 0,  "source_id": 0,  "company_id": 0,  "external_job_id": "string",  "url": "string",  "apply_url": "string",  "title": "string",  "location": "string",  "locations": [    {      "kind": "country",      "code": "string",      "name": "string",      "admin1_name": "string",      "country_code": "string"    }  ],  "workplace": "remote",  "employment_type": "full_time",  "level": "intern",  "experience_min_years": 0,  "experience_max_years": 0,  "sponsors_visa": true,  "language": "string",  "salary_raw": "string",  "salary_min": 0,  "salary_max": 0,  "salary_currency": "string",  "salary_period": "year",  "posted_at": "string",  "expires_at": "string",  "content_hash": "string",  "first_seen_at": "string",  "closed_at": "string",  "created_at": "string",  "updated_at": "string",  "description_md": "string",  "description_html": "string",  "company": {    "id": 0,    "name": "string",    "website_url": "string",    "logo_url": "string",    "industry": "string",    "size_range": "string",    "founded_year": 0,    "is_agency": true,    "linkedin_id": "string",    "created_at": "string",    "updated_at": "string"  }}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}
{  "type": "string",  "title": "string",  "status": 0,  "detail": "string",  "errors": [    {      "field": "string",      "message": "string"    }  ]}