---
title: "Update your profile and account preferences"
method: PATCH
path: "/users/{id}"
tags: ["Users"]
---

# Update your profile and account preferences

`PATCH /users/{id}`

## Request

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

Update the signed-in person's own profile, identified by `id` from
`fetchCurrentUser`. Requires that person's permitted write access;
another person's profile cannot be edited through this operation.

Send only the fields the person wants changed: `name`, `about`,
`handle`, `mobile`, `contact_info`, `account_mode`,
`default_record_mode`, or the `place` relationship. Profile and
contact changes may affect what other people see. Confirm the
requested changes before writing. Returns the updated user.

This does not change the login email, password, avatar, or SMS
notification preferences, and changing a mobile here does not verify
it. Password and phone verification flows stay on PurpleGrain's own
pages; use `updateUserAvatar` for the profile picture and
`updateSmsPreferences` for SMS notification preferences.

## Path parameters

- `id` string, required

## Query parameters

- `include` string

## Request body

- UserUpdateModelAttributes: 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: `UserUpdateModel` sent as application/vnd.api+json.
  - `data` object, required
    - `name` string, nullable
    - `about` string, nullable
    - `handle` string
    - `account_mode` 'grower' | 'contractor', nullable
    - `default_record_mode` 'ai' | 'general', nullable: Which record type the composer opens on: `ai` (PurpleGrain AI, the default) or `general`. Only present for the current user. A null value is ignored on update.
    - `mobile` string, nullable
    - `contact_info` object
      - `mobile` string, nullable
      - `phone` string, nullable
      - `facebook` string, nullable
      - `url` string, nullable
      - `email` boolean, nullable
    - `place` object, nullable
      - `id` string, nullable
      - `type` 'city' | 'address', 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).

- UsersShowResponseAttributes
  - `data` UserModelAttributes: User in attributes-adapter shape (Accept application/json); fields at top level.
    - `id` string
    - `type` string
    - `email` string, email
    - `name` string, nullable
    - `about` string, nullable
    - `account_mode` 'grower' | 'contractor', nullable: Grower vs Contractor mode; only present for the current user.
    - `default_record_mode` 'ai' | 'general': Which record type the composer opens on: `ai` (PurpleGrain AI, the default) or `general`. Only present for the current user. A null value is ignored on update.
    - `free_trial_used` boolean, nullable: Whether the free trial has been used. Only populated when viewing your own profile; null otherwise.
    - `mobile` string, nullable: Canonical login mobile (E.164); only present for the current user.
    - `mobile_verified` boolean, nullable: Whether the mobile has been verified by SMS one-time code; only present for the current user.
    - `sms_opted_out` boolean, nullable: Whether SMS to this account is silenced (by replying STOP, or from the account screen); only present for the current user.
    - `handle` string
    - `url` string
    - `avatar_url` string, nullable
    - `avatar_thumb_url` string, nullable
    - `updated_at` string, date-time
    - `created_at` string, date-time
    - `contact_info` object
      - `mobile` string, nullable
      - `phone` string, nullable
      - `facebook` string, nullable
      - `url` string, nullable
      - `email` boolean, nullable
    - `city` object: Inlined when include=city
    - `place` object: Inlined when include=place
    - `services` unknown[], nullable: Inlined when include=services
      - unknown
    - `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.

## Changes

- **2026-10-31** `370fe2f3ed4f`: 2 info
  - the request property `data/attributes/default_record_mode` became nullable (media type: application/vnd.api+json)
  - the request property `data/default_record_mode` became nullable (media type: application/json)
- **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** `c1bc18b1cdbc`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/purplegrain/apis/purple-grain-api/changes/users/: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)
