---
title: "Update an existing contractor service listing"
method: PATCH
path: "/services/{id}"
tags: ["Services"]
---

# Update an existing contractor service listing

`PATCH /services/{id}`

## Request

- Base URL: `https://api.purplegrain.com/api/v1`
- URL: `https://api.purplegrain.com/api/v1/services/{id}`
- Auth: HTTP bearer

Change the service listing identified by id, such as its name, description, active or inactive status, quoted cost and currency, service radius, tags or related place. Send only the requested changes in data; omitted fields stay unchanged. Read the listing first and confirm the changes with the person. The caller must be able to act for the listing's owner, and an MCP connection must allow writes. Returns the updated service. This does not book a contractor, send a message or take payment. Use updateServiceImage for the listing's photo.

## Path parameters

- `id` string, required

## Query parameters

- `include` string

## Request body

- ServiceUpdateModelAttributes: Flat attributes-adapter request body (Content-Type application/json). Attribute fields and relationships live directly under `data` (no nested `attributes` / `relationships` envelopes). Each relationship is a resource identifier object ({ id, type }). Equivalent JSON:API (nested) shape: `ServiceUpdateModel` sent as application/vnd.api+json.
  - `data` object, required
    - `name` string
    - `slug` string, nullable
    - `description` string, nullable
    - `status` 'active' | 'inactive', nullable
    - `cost` string, nullable
    - `currency` string, nullable
    - `radius` integer, nullable
    - `tags` string[], nullable: Hashtag-style labels (they work like Twitter hashtags) — free-form lowercase tokens stored without a leading hash, e.g. ["slashing", "north-paddock"]. Reusing a tag groups records together, and the /services/search term parameter matches tags case-insensitively, so a tag aggregates everything labelled with it. Send an empty array to clear every tag (null is accepted and stored as an empty array).
    - `owner` object, nullable
      - `id` string, nullable
      - `type` 'user' | 'organization', nullable
    - `place` object, nullable
      - `id` string, nullable
      - `type` 'city' | 'address', nullable
    - `category` object, nullable
      - `id` string, nullable
      - `type` string, nullable

## Response `200`

Update. When the `include` query parameter is used, the response may contain an `included` array (JSON:API) or inlined related resources (application/json).

- ServicesShowResponseAttributes
  - `data` ServiceModelAttributes: Service in attributes-adapter shape (Accept application/json); fields at top level.
    - `id` string
    - `type` string
    - `slug` string
    - `name` string
    - `status` 'active' | 'inactive'
    - `cost` string, nullable
    - `currency` string, nullable
    - `description` string, nullable
    - `tags` string[]: Hashtag-style labels (they work like Twitter hashtags) — free-form lowercase tokens stored without a leading hash, e.g. ["slashing", "north-paddock"]. Reusing a tag groups records together, and the /services/search term parameter matches tags case-insensitively, so a tag aggregates everything labelled with it. Always an array — the API never returns null.
    - `radius` integer, nullable
    - `url` string
    - `image_url` string, nullable
    - `image_thumb_url` string, nullable
    - `updated_at` string, date-time
    - `created_at` string, date-time
    - `city` object: Inlined when include=city
    - `place` object: Inlined when include=place
    - `owner` object: Inlined when include=owner
    - `category` object: Inlined when include=category
    - `location` object: Inlined when include=location

## Other responses

- `400`: The `include` query parameter names a relationship path this endpoint does not take (JSON:API 1.1, "Inclusion of Related Resources"). One error with `code: "unknown_include"` and `source.parameter: "include"`; its `detail` names the refused path(s) and the paths the endpoint allows (the parameter's `x-include-paths`). An endpoint whose serializer whitelists no relationship refuses any path. Rolling out as monitor then enforce: until the API's `JSONAPI_UNKNOWN_INCLUDE` is `reject`, an unknown path is dropped and logged instead.
- `409`: Conflict — the JSON:API request document names a different resource from the URL (JSON:API 1.1 §9.2 and §9.3). `errors[0].code` is `type_mismatch` when `data.type` is not one of this endpoint's resource types (`source.pointer` is `/data/type`), or `id_mismatch` when, on an update, `data.id` is not the id in the URL (`/data/id`). Only a body sent as `application/vnd.api+json` is checked; the flat `application/json` body has no resource object. Nothing was changed. Request validation runs first: a `data.type` outside the operation's documented enum, or missing where the schema requires it, is a `400` before this check, so the 409 is seen where the documented type is a free string, or when validation is off.
- `415`: Unsupported Media Type — the request's `Content-Type` is `application/vnd.api+json` with a media type parameter the JSON:API media type does not allow (anything other than `ext` and `profile`; `; charset=utf-8` is the usual one). Send the bare media type. The error's `code` is `unsupported_media_type` and `source.header` is `Content-Type`. Until the switch is on (`JSONAPI_CONTENT_TYPE_PARAMS=reject`) such a request is let through and logged; the operation documents the response it will have.
- `422`: Errors

## Changes

- **2026-10-30** `143d65e52617`: 3 info
  - added the non-success response with the status `400`
  - added the non-success response with the status `409`
  - added the non-success response with the status `415`
- **2026-10-30** `92c4663a1266`: 6 warning, 6 info
  - removed the optional property `_meta` from the response with the `422` status (media type: application/json)
  - removed the optional property `_meta` from the response with the `422` status (media type: application/vnd.api+json)
  - removed the optional property `errors/items/onboarding` from the response with the `422` status (media type: application/json)
  - removed the optional property `errors/items/onboarding` from the response with the `422` status (media type: application/vnd.api+json)
  - …8 more
- **2026-10-30** `c1bc18b1cdbc`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/purplegrain/apis/purple-grain-api/changes/services/:id/patch.md)

---

[API](https://skmtc.dev/purplegrain/apis/purple-grain-api.md) - [All operations](https://skmtc.dev/purplegrain/apis/purple-grain-api/llms.txt) - [OpenAPI document](https://skmtc.dev/purplegrain/apis/purple-grain-api/revisions/370fe2f3ed4f?raw)
