> ## Client identification
>
> Requests to this API should include a `Client-Platform` header alongside
> `Authorization`, identifying the application, tool, or model making the
> request. This is used for integration support and analytics.
>
> Use your model or tool name, for example `claude-code` or `chatgpt`.
>
> Example:
>
> ```bash
> curl https://api.salaryboxapp.com/... \
>   -H "Authorization: Api-Key <api-key>" \
>   -H "Client-Platform: claude-code"
> ```

# Writing Data

These rules apply to every endpoint that adds or changes data. Each endpoint page only lists what is specific to it.

## Authentication

Send `Authorization: Api-Key <key>` on every request.

## Who a write acts as

The key acts as the user who created it, re-checked on every write. That user must still be active, not deleted, in the company, and have the **Admin** or **Custom** role. Otherwise you get `403`: "The user who created this API key no longer has access to make changes. Ask an admin to create a new API key."

* **Admin** keys act company-wide.
* **Custom-role** keys need the permission named on each endpoint. They can only act on staff in their My Team scope: a shared branch, and a shared department if they have departments. Never Admins, never themselves. Otherwise `403`.
* Everything written records that user as creator/updater.

## Which staff can be changed

Active staff with the role Employee, Branch Admin, Attendance Manager, Advanced Attendance Manager or Custom. Admins and Accountants can't be changed through the API.

* Unknown or ineligible `salarybox_id`: `404` "Staff not found."
* Inactive staff: `400` "Staff is inactive. Reactivate them in the SalaryBox app first."

## Identifying things

There are no internal IDs. Staff are identified by `salarybox_id`. Branches, departments and custom fields are identified by their **exact name** as shown in SalaryBox (see List Branches, List Departments, List Custom Fields).

* Matching is exact and case-sensitive, with no trimming: `gurgaon hq` does not match `Gurgaon HQ`.
* An unknown name, or a name shared by two live records, gives `400` on that field.
* Name lists are JSON arrays, up to 50 names, no repeats.

## Request format

* The body is a JSON object. Unknown fields give `400` "Unknown field."
* Types are strict: strings are strings, booleans are `true`/`false`, numbers are numbers (not numeric strings), lists are arrays.
* Dates are `YYYY-MM-DD` only. Anything else gives "Date must be in YYYY-MM-DD format."

## Repeating a request

Every write is applied each time it is sent. Sending an add request twice adds twice, unless the data itself prevents it.

* Add Staff rejects a phone number that already exists in the company: `400` on `phone_number`, "A staff member with this phone number already exists."
* Add Employment History has no such check: the same entry sent twice is stored twice.
* Create Staff Invite Link returns a link each time.
* Update Staff, Add or Replace Staff Note, Set Staff Permissions, Set Staff Role and Set Staff Add-on Access set values, so repeating them is safe.

If a write times out or the connection drops, check with the matching read endpoint (for example List Staff by phone number, or Employment History) before sending it again.

## Errors

```json
{ "message": "Invalid Data", "status": false, "errors": { "field": ["What is wrong"] } }
```

* `errors` is `null` when the problem is not about one field.
* Validation errors have message "Invalid Data" (format checks) or "Invalid data" (company checks), with `errors` keyed by field.
* `401` bodies come from the gateway (`message` and `detail`: "Invalid Api-Key").

## Limits

* 500 write requests per day per company per endpoint, separate from reads. Beyond that you get `429` with the wait time in the message.
* Create Staff Invite Link also allows 1 request per 10 seconds and 20 per hour.
* A company whose subscription has lapsed is blocked, as for reads.