---
title: "Create a fee"
method: POST
path: "/api/v1/fees"
tags: ["Fees"]
---

# Create a fee

`POST /api/v1/fees`

Use this endpoint to create a fee on a project.

A **fee** represents a financial charge tied to a project — typically a placement fee earned when a candidate is successfully hired.

**Splits are required.** Every fee must declare how its revenue is distributed across fee earners via `splits`, and the shares must total exactly 100%.

**Placement side effect:** when `personId` refers to a person who is a candidate on the project and has no placement yet, this call **also creates a placement**, links the fee to it, and emits a placement webhook. If the candidate already has a placement, the fee is linked to the existing one. If the person is not a candidate on the project, the fee is created without a placement. `companyContactId` and `type` are only used when a placement is created by this call; `type` defaults to `permanent`.

**Currency conversion:** when `currency` differs from your agency currency, `defaultAmount` is computed automatically using the current exchange rate, and the agency currency is snapshotted onto the fee as `defaultCurrency`.

**Defaults:** `projectFeeStatus` defaults to `projected`; `paid` also sets `paidAt` and `invoiced` also sets `invoicedAt`. `invoiceAccountCode` defaults from your agency settings and is not settable on create.

**Not supported:** contract fees — creating a fee for a candidate with an existing contract placement returns `422`.

**What you get back:**
The created fee in the same shape as the fee detail endpoint, so a follow-up GET returns identical fields.

## Request body

- object
  - `projectId` string, uuid, required — Project the fee belongs to
  - `amount` string, required — Fee amount as a decimal string — not a JSON number
  - `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', required — Fee currency (ISO 4217)
  - `feeDate` string, required — Effective date of the fee (YYYY-MM-DD)
  - `projectFeeStatus` 'projected' | 'earned' | 'invoiced' | 'paid' — Fee lifecycle status — defaults to `projected`. `paid` also sets `paidAt`; `invoiced` also sets `invoicedAt`
  - `personId` string, uuid — Links the fee to a person. When the person is a candidate on the project without an existing placement, a placement is created and linked to the fee
  - `feeTypeId` string, uuid — Fee type ID
  - `notes` string, nullable — Fee notes
  - `companyContactId` string, uuid — Only used when this call creates a placement
  - `type` 'permanent' | 'placement' — Placement type — only used when this call creates a placement. Defaults to `permanent`. Legacy `placement` is accepted and normalised to `permanent`. Contract fees are not supported by this endpoint
  - `splits` object[], required — How the fee revenue is distributed across fee earners — shares must total exactly 100
    - `feeEarnerId` string, uuid, required — User ID of the fee earner receiving this split
    - `feeTypeId` string, uuid, required — Fee type ID for this split
    - `share` string, required — Share percentage as a decimal string — all splits together must total exactly 100
    - `notes` string, nullable — Notes on this split

## Response `201`

Fee created

- object
  - `status` 'ok', required
  - `data` object, required
    - `id` string, uuid, required — Fee ID
    - `projectId` string, uuid, required — Associated project ID
    - `personId` string, uuid, nullable, required — Associated person/candidate ID
    - `placementId` string, uuid, nullable, required — Associated placement ID
    - `createdById` string, uuid, required — User ID who created the fee
    - `feeType` object, nullable, required — Associated fee type; null when none is assigned
      - `id` string, uuid, required — Fee type ID
      - `name` string, required — Fee type name
      - `deletedAt` string, nullable, required — ISO 8601 — when the fee type was soft-deleted; null when active
    - `feeDate` string, nullable, required — Effective date of the fee (YYYY-MM-DD)
    - `amount` string, nullable, required — Fee amount as decimal string
    - `defaultAmount` string, nullable, required — Default/original fee amount as decimal string
    - `currency` string, required — Fee currency code
    - `defaultCurrency` string, required — Agency base currency code
    - `projectFeeStatus` 'projected' | 'earned' | 'invoiced' | 'paid', required — Fee lifecycle status
    - `notes` string, nullable, required — Fee notes
    - `externalInvoiceNumber` string, nullable, required — External invoice number
    - `invoiceAccountCode` string, nullable, required — Accounting account code for invoices
    - `paidAt` string, nullable, required — ISO 8601 — when the fee was paid
    - `invoicedAt` string, nullable, required — ISO 8601 — when the fee was invoiced
    - `createdAt` string, nullable, required — ISO 8601 — creation date
    - `updatedAt` string, nullable, required — ISO 8601 — last update date
    - `deletedAt` string, nullable, required — ISO 8601 soft-delete timestamp. `null` for live fees; populated for tombstones (only returned when `includeDeleted=true`)
    - `splits` object[], required — Fee splits across earners — may be empty
      - `id` string, uuid, required — Split ID
      - `feeEarner` object, required — User who earns this split
        - `id` string, uuid, required — Fee earner (user) ID
        - `name` string, nullable, required — Fee earner display name
        - `email` string, nullable, required — Fee earner email address
      - `feeType` object, nullable, required — Associated fee type; null when none is assigned
        - `id` string, uuid, required — Fee type ID
        - `name` string, required — Fee type name
        - `deletedAt` string, nullable, required — ISO 8601 — when the fee type was soft-deleted; null when active
      - `share` string, nullable, required — Share percentage as decimal string
      - `notes` string, nullable, required — Notes on this split
      - `deletedAt` string, nullable, required — ISO 8601 soft-delete timestamp. `null` for live splits. Only populated for splits of a deleted fee tombstone (returned when `includeDeleted=true`)

## Other responses

- `401` — Unauthorized - missing or invalid API key
- `404` — Project, person, fee type or company contact not found in your agency
- `422` — Validation error - the request body or query parameters failed validation
- `429` — Too many requests - the caller has exceeded the per-agency rate limit for the tier this endpoint counts against (default per minute: 1200 read / 400 write / 60 upload). Inspect the `RateLimit-*` headers — returned on every response, not only on 429s — and back off until the window resets. See the "Rate limits" section of the introduction for details.
- `500` — Internal server error

## Changes

- **2026-09-15** `1131a0974b93` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/recruitwithatlas/apis/atlas-api/changes/api/v1/fees/post.md)

---

[API](https://skmtc.dev/recruitwithatlas/apis/atlas-api.md) · [All operations](https://skmtc.dev/recruitwithatlas/apis/atlas-api/llms.txt) · [OpenAPI document](https://skmtc.dev/recruitwithatlas/apis/atlas-api/revisions/bfc1118eeb5d?raw)
