Fees

Use this endpoint to partially update an existing fee. Omitted fields are left untouched.

Amount & currency: amount is a decimal string (matching what the read endpoints return). Changing amount, currency or feeDate recomputes defaultAmount in the agency currency using the FX rate for the fee date.

Status: fees progress projected → earned → invoiced → paid. Setting projected is rejected with a 422 when the fee would still be linked to a placement after the update — including a placement picked up by relinking personId in the same request (such fees are at least earned). A change to paid stamps paidAt; a change to invoiced stamps invoicedAt.

Clearing text fields: notes and invoiceAccountCode are cleared by sending null. Empty and whitespace-only strings are rejected with a 422 rather than silently stored — otherwise the value written ("") and the value read back (null) would disagree.

Relinking: personId requires projectId, which must match the fee’s own project (a fee cannot be moved to another project — 422 otherwise), and rewires placementId to that candidate’s placement on the project (or null when there is none). Passing personId: null detaches the person and the placement.

Splits: the splits array is a full replacement, not a merge. [] removes every split. In a non-empty array, items with an id update that split, items without an id create a new one, and any existing split not listed is removed. Shares must total exactly 100, and the same split id may appear at most once per request (422 otherwise).

Body rules: the body must be sent with Content-Type: application/json (charset parameters are accepted) — any other content type is rejected with 415 so a payload is never silently discarded. A request with no body at all is rejected with 422. An explicit empty object ({}) is the documented no-op: it returns 200 and leaves every field unchanged.

What you get back: The full updated fee record with all of its splits — the same shape as GET /api/v1/fees/{id}.

patch/api/v1/fees/{id}

Request

Path parameters

idstring uuid required

Fee ID

Example:550e8400-e29b-41d4-a716-446655440000

Request body

amountstring

Fee amount as a decimal string. Changing amount, currency or feeDate recomputes defaultAmount in the agency currency using the FX rate for the fee date.

currency'USD' | 'EUR' | 'JPY' | 'GBP' | 'AUD' | 'CAD' | 'CHF' | 'CNY' | 'HKD' | 'NZD' | 'SEK' | 'NOK' | 'MXN' | 'SGD' | 'RUB' | 'ZAR' | 'TRY' | 'BRL' | 'INR' | 'KRW' | 'DKK' | 'PLN' | 'ILS' | 'HUF' | 'CZK' | 'RON' | 'THB' | 'MYR' | 'IDR' | 'VND' | 'PHP' | 'SAR' | 'AED' | 'QAR' | 'KWD' | 'JOD' | 'CLP' | 'COP' | 'PEN' | 'ARS' | 'UYU' | 'CRC' | 'PKR' | 'BDT' | 'LKR' | 'EGP' | 'NGN' | 'TWD' | 'KES' | 'GHS' | 'UGX' | 'TZS' | 'MAD' | 'BWP' | 'BGN' | 'UAH' | 'KZT' | 'GEL' | 'ISK' | 'BHD' | 'OMR'

Fee currency code (ISO 4217)

feeDatestring

Effective date of the fee (YYYY-MM-DD)

projectFeeStatus'projected' | 'earned' | 'invoiced' | 'paid'

Fee lifecycle status. projected is rejected (422) when the fee would still be linked to a placement after this update (including a placement picked up via personId relinking) — use earned. A change to paid stamps paidAt; a change to invoiced stamps invoicedAt.

notesstring nullable

Fee notes. null clears the field; an empty or whitespace-only string is rejected (422).

feeTypeIdstring uuid

Fee type ID — must belong to the agency

personIdstring uuid nullable

Relinks the fee to a person. Requires projectId and relinks placementId to that candidate’s placement (or null when the person has no placement on the project). null detaches the person and the placement.

projectIdstring uuid

Project used to resolve the candidate placement when personId is set. Must match the fee's own project — a fee cannot be moved to another project (422 otherwise).

invoiceAccountCodestring nullable

Accounting account code for invoices. null clears the field; an empty or whitespace-only string is rejected (422).

Example request

{
  "amount": "15000.00",
  "currency": "GBP",
  "feeDate": "2025-06-15",
  "projectFeeStatus": "earned",
  "feeTypeId": "990e8400-e29b-41d4-a716-446655440004",
  "personId": "aa0e8400-e29b-41d4-a716-446655440005",
  "projectId": "550e8400-e29b-41d4-a716-446655440000",
  "splits": [
    {
      "id": "770e8400-e29b-41d4-a716-446655440002",
      "feeEarnerId": "880e8400-e29b-41d4-a716-446655440003",
      "feeTypeId": "990e8400-e29b-41d4-a716-446655440004",
      "share": "60.00"
    }
  ]
}

Response

Fee updated

status'ok' required

Example response

{
  "data": {
    "feeType": {
      "name": "Placement Fee"
    },
    "feeDate": "2025-06-15",
    "amount": "15000.00",
    "defaultAmount": "15000.00",
    "currency": "GBP",
    "defaultCurrency": "GBP",
    "projectFeeStatus": "earned",
    "splits": [
      {
        "feeEarner": {
          "name": "Jane Smith",
          "email": "jane@agency.com"
        },
        "feeType": {
          "name": "Placement Fee"
        },
        "share": "60.00"
      }
    ]
  }
}

Changes

    • info

      added the non-success response with the status

    • info

      added the optional property to the response with the status