# Wenmar Jobs API

Read-only, free, no key. JSON over HTTPS, open to any origin (CORS). Field names are stable:
fields may be added, never renamed or removed. Money is in dollars as numbers; codes are the
lists below; times are UTC, `2026-10-15T12:00:00Z`.

## GET /jobs.json

Live jobs matching the filters, newest first unless `sort` says otherwise, 30 a page.

### Parameters

| Parameter | Values |
|---|---|
| `q` | Text: the title, description or shop name |
| `near` | A postal code (`L5B`) or a city slug, for "within `km` of" |
| `km` | 5, 10, 25, 50; 25 by default with `near` |
| `tag` | Repeatable; all of: a specialty, credential or perk code |
| `cert` | Repeatable; jobs open to someone holding these credentials |
| `pay_structure` | `hourly`, `flat_rate`, `flat_rate_guarantee`, `salary` |
| `min_pay` | Dollars an hour, 1–500; compared with the job's hourly equivalent |
| `job_type` | `full_time`, `part_time`, `contract`, `apprenticeship` |
| `employer` | `independent`, `dealership`, `chain`, `fleet` |
| `shift` | `days`, `evenings`, `nights`, `weekends` |
| `bbox` | `w,s,e,n`, the map's "search this area" |
| `sort` | `new` (default), `pay`, `near` (with `near`), `expiring` |
| `view` | `list` or `map`; no effect on the JSON answer |
| `page` | 1 and up |
| `region` | A region slug; default `gta` |

An unknown parameter or value is a 400.

### Answer

| Field | Meaning |
|---|---|
| `jobs` | The page's jobs, as below |
| `total` | Matching jobs, on every page |
| `page` | This page, 1 and up |
| `pages` | How many pages the search has |
| `per_page` | 30 |
| `query` | The search as the API understood it, canonical |
| `next` | The next page's address, or null |
| `prev` | The previous page's address, or null |
| `region` | `slug`, `name` and `currency` of the region searched |

### A job in `jobs`

| Field | Meaning |
|---|---|
| `id` | The job's public id, 8 characters |
| `url` | The job's page |
| `json` | The job's JSON twin |
| `title` | The title as posted |
| `shop` | `name` and `url` of the shop's page |
| `city` | The city the job is in |
| `location` | `lat` and `lng`, WGS 84 decimal degrees |
| `distance_km` | From `near`, when there is one; one decimal |
| `pay` | `min` and `max` in dollars, `currency`, `unit` (`hour`, `flag_hour`, `year`), `structure`, `guaranteed_hours` (with a guarantee) and `label` in words |
| `job_type` | `full_time`, `part_time`, `contract`, `apprenticeship` |
| `tags` | `code`, `label` and `kind` (`specialty`, `credential`, `perk`, `job_type`) |
| `published_at` | When it went live |
| `expires_at` | When it comes down |

## GET /jobs/{id}.json and /jobs/{id}.md

One job, as JSON or as Markdown. The JSON fields:

| Field | Meaning |
|---|---|
| `id` | The job's public id |
| `url` | The job's page |
| `status` | `live` or `expired`; a closed job answers as `expired` |
| `title` | The title as posted |
| `published_at`, `expires_at`, `updated_at` | UTC times |
| `expires_in_days` | Whole days left, for a live job |
| `shop` | `name`, `url`, `employer_type`, `size`, `bays` |
| `location` | `lat` and `lng`, WGS 84 |
| `pay` | `min` and `max` in dollars, `unit`, `structure`, `currency` |
| `specialties`, `credentials`, `perks`, `shifts` | Lists of codes |
| `job_type` | A code |
| `apply` | `email`, `phone` and `url` as posted, null when the job is no longer live |
| `existing_vacancy`, `uses_ai_screening` | The poster's answers, true or false |

A closed or expired job still answers, with its `status`; a removed one is a 410.

## GET /feed.xml

Atom 1.0, the newest 30 jobs matching the same parameters, lenient: unknown ones are
ignored. `region` works as on the API.

## GET /jobs.geojson

The map's data: every matching live job as a GeoJSON point, properties `id`, `title`,
`shop`, `city`, `pay_label`, `pay`, `tags`, `url`.

## GET /jobs.xml

The region's live jobs as XML, for the free aggregators (Talent.com, Adzuna, Jooble) that
take a feed address. Not paged: up to 5,000 jobs per feed. The job pages are what search
engines index, so the feed itself is `noindex` and cached for 15 minutes.

The board's filters apply the same as on the board (`q`, `tag`, `specialty`, `near`,
`within_km`, `min_pay`, `region`…); an unknown parameter means nothing. `sort` is always
`new`. An unknown `region` is a 404.

| Parameter | Meaning |
|---|---|
| `format` | `jooble` for Jooble's shape. Any other value is a 400 with a plain-text reason. |
| `source` | A short word (`[a-z0-9_-]{1,20}`) naming the aggregator. `utm_source` is added to every job's URL so the analytics can tell the aggregators apart. |

### The default shape

`<source>` with a `<job>` per job — the shape Talent.com documents:

- `referencenumber`, `title`, `company`, `city`, `state` (the province's name),
  `country`, `postalcode` (the area when the street is hidden), `streetaddress` (only
  when the street is shown), `dateposted`, `expirationdate`, `url`, `description`,
  `jobtype`, `category`, `isremote`, and a `<salary>` block with `salary_min`,
  `salary_max`, `salary_currency`, `period` (`hour` or `year`) and `type`.

Every job's `url` is the site's job page. Text is wrapped in CDATA.

### Jooble's shape (`?format=jooble`)

`<jobs>` with a `<job id>` per job:

- `link`, `name`, `region` (`City, Province`), `description`, `pubdate`, `updated`,
  `expire`, `salary`, `company`, `jobtype`.

The dates are `DD.MM.YYYY` in the region's time zone. `salary` is the range as the board
labels it (`$38–45/h`, `$70k`). An `apprenticeship` is Jooble's `internship`.

## POST /mcp

The Model Context Protocol over HTTP, stateless: one JSON-RPC message per request, no
sessions, no streams, no stored state. The board as four tools: three read-only ones,
and `post_job`, which makes a draft.

### Revisions

The server speaks the handshake revisions `2025-11-25`, `2025-06-18` and `2025-03-26`
(the client opens with `initialize`, then sends the version it was given in the
`MCP-Protocol-Version` header) and the revision without a handshake, `2026-07-28`
(every request names its version in `params._meta` and in the `MCP-Protocol-Version`,
`Mcp-Method` and, for a tool call, `Mcp-Name` headers; results are marked complete).
A version the server does not speak is a 400, JSON-RPC code `-32022`, naming the ones
it does.

### Tools

| Tool | The same as | Arguments |
|---|---|---|
| `search_jobs` | `GET /jobs.json` | The API's parameters; arrays repeat the key |
| `get_job` | `GET /jobs/{id}.json` | `id`, the 8-character job id |
| `list_tags` | — | `region`, a slug; default `gta`. The credentials are the region's |
| `post_job` | `POST /post` (the draft step) | The post form's fields: text as strings, lists as arrays, the checkboxes as booleans, `agree_to_rules` for the form's agree box |

Each tool answers the same JSON its HTTP twin sends, as the result's `structuredContent`.
A tool that fails is a tool answer — `isError` true and the twin's error as
`structuredContent` — not a JSON-RPC error.

`post_job` stores the post as a draft, the same row `POST /post` stores: nothing is
published and no mail is sent. Its answer is `status` `draft`, the job `id`, a
`review_url` of the form `/post?draft=…` for the shop to check the post and press
confirm, and `next`, the sentence of the tool's description the answer repeats. A
link is then mailed to `email`, and the post goes live once it is followed; a first
post from an address is also checked by a person. A post the form would not take is a
tool answer with `error` `invalid` and a `details` array of `{ "field", "message" }`
pairs, every field the form would mark. The tool is
limited to 10 calls an hour per client, on top of the endpoint's own limit; over it,
the tool answer's `error` is `rate_limited`.

`GET` and `DELETE /mcp` are 405; `OPTIONS` is the usual preflight. The endpoint is
limited to 60 requests a minute per client, on top of the board's own ceiling.

## Errors

`{"error":{"code":"…","message":"…"}}` with `bad_request` (400), `not_found` (404), `gone`
(410), `internal` (500).

## Codes

### Specialties

| Code | Label |
|---|---|
| `general` | General repair |
| `diesel` | Diesel and heavy duty |
| `ev` | EV and hybrid |
| `collision` | Collision and body |
| `tires` | Tires and alignment |
| `lube` | Quick lube and maintenance |
| `motorcycle` | Motorcycle and powersports |
| `rv` | RV and trailer |
| `transmission` | Transmission and driveline |
| `electrical` | Diagnostics and electrical |
| `service_advisor` | Service advisor |
| `parts` | Parts counter |
| `office` | Office and reception |
| `manager` | Shop or service manager |
| `porter` | Lot porter and shop helper |
| `detailer` | Detailing |
| `driver` | Tow and shuttle driver |

### Credentials (Ontario)

| Code | Label |
|---|---|
| `on_310s` | Automotive Service Technician (310S) |
| `on_310t` | Truck and Coach Technician (310T) |
| `on_310b` | Auto Body and Collision Damage Repairer (310B) |
| `red_seal` | Red Seal endorsement |
| `apprentice` | Registered apprentice |
| `mvis` | Can sign safety standards certificates |
| `on_g` | Full G driver's licence |
| `on_dz` | DZ licence |
| `on_az` | AZ licence |

### Perks

| Code | Label |
|---|---|
| `parking` | Free parking |
| `transit` | Near transit |
| `tools_provided` | Shop tools provided |
| `tool_allowance` | Tool allowance |
| `paid_training` | Paid training |
| `oem_training` | OEM / dealer training |
| `apprenticeship` | Sponsors apprentices |
| `newcomer_friendly` | Newcomers welcome |
| `benefits` | Health benefits |
| `rrsp` | RRSP or pension match |
| `four_day_week` | Four-day week |
| `no_weekends` | No weekends |
| `overtime` | Overtime available |
| `uniforms` | Uniforms provided |
| `heated_bays` | Heated bays |
| `modern_equipment` | Modern scan tools and lifts |

### Pay structures

| Code | Label |
|---|---|
| `hourly` | Hourly |
| `flat_rate` | Flat rate |
| `flat_rate_guarantee` | Flat rate with guarantee |
| `salary` | Salary |

### Pay units

| Code | Label |
|---|---|
| `hour` | Per hour |
| `flag_hour` | Per flag hour |
| `year` | Per year |

### Job types

| Code | Label |
|---|---|
| `full_time` | Full time |
| `part_time` | Part time |
| `contract` | Contract |
| `apprenticeship` | Apprenticeship |

### Employer types

| Code | Label |
|---|---|
| `independent` | Independent shop |
| `dealership` | Dealership |
| `chain` | Chain |
| `fleet` | Fleet |

### Shifts

| Code | Label |
|---|---|
| `days` | Days |
| `evenings` | Evenings |
| `nights` | Nights |
| `weekends` | Weekends |
