ExpensesUpdate an employee expense (v2)

Update an employee expense (v2)

Replaces the amounts, date, currency, type, payment method, billability, comment, project AND contract of an unapproved employee expense (v2 route).

A full replacement (PUT), like PUT /v1/employee-expense, with one contract difference: v2 also takes ProjectSubContractID, and it must be sent — an omitted contract is 0 and fails with 37042. Because the contract is part of the body, v2 can move an expense to another contract or another project (send a contract of that project); v1 cannot. The owner cannot be changed. The response body is JSON null.

Use it to:

  • Expense app letting an employee correct an expense before approval, including its project / contract
  • Receipt-capture integration filling in amount / VAT / currency after OCR of the uploaded receipt

Not for:

  • Changing the owner — delete and re-create the expense
  • Changing receipts — the attach / delete image and file operations

Rules across fields:

  • R1: ProjectSubContractID must be a contract of ProjectID (0 / omitted included → rejected). To change the project, send a contract of the new project.
  • R2: ProjectSubContractID must not be a prepaid-services contract. An inactive contract is accepted on update.
  • R3: ExpenseTypeID must belong to the owner's legal entity.
  • R4: Date must be inside the owner's employment and not in a closed accounting period.

Behaviour:

  • Idempotent.
  • Same body → same stored values; last write wins (no concurrency token).
  • Safe to retry.
  • Duplicates: n/a
  • Changes are visible to the next read immediately.

Call first: GET /v2/employee-expense/{employeeExpenseID}, GET /v1/contract

Next: GET /v2/employee-expense/{employeeExpenseID}

See also: PUT /v1/employee-expense, DELETE /v2/employee-expense/{employeeExpenseID}

Not yet documented: examples; request → cross field rules → R2.

curl -X PUT "https://app3.timelog.com/your-account/api/v2/employee-expense" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
  "EmployeeExpenseID": 123,
  "Date": "2024-12-25T10:00:00Z",
  "Amount": 3.14,
  "VatAmount": 3.14,
  "IsBillable": true,
  "Comment": "null",
  "ExchangeRate": 3.14,
  "CurrencyID": 123,
  "ExpenseTypeID": 123,
  "PaymentMethodID": 123,
  "ProjectID": 123,
  "ProjectSubContractID": 123
}'
"null"
PUT
/v2/employee-expense
PUT
Base URLstring

Target server for requests. Edit to use your own host.

Bearer Token
Bearer Tokenstring
Required

OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp). The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user. > Not yet documented: how to obtain a token. > Not yet documented: token lifetime.

OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp). The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user. > Not yet documented: how to obtain a token. > Not yet documented: token lifetime.
Content-Typestring
Required

The media type of the request body

Options: application/json
EmployeeExpenseIDinteger

The expense to update (from GET /v2/employee-expense). Identifier of this object, unique per TimeLog site. - ≤ 0 — 400, Code 200. - unknown ID — 500, Code 100 (the expense lookup throws a data-access exception).

Format: int32
Datestring

The expense (receipt) date. Date only — no time component. - a time component is sent — Truncated to the date (DateTime.Date). Must be inside the owner's employment (38022) and not in a closed accounting period (37699). > Not yet documented: constraints → timezone.

Format: date-time
Amountnumber

The expense amount, in the currency CurrencyID. Unit: currency of CurrencyID - omitted — Written as 0 (full replacement). NotNull on a non-nullable double never fails: 0 and negative amounts pass.

Format: double
VatAmountnumber

The VAT part, in the currency CurrencyID. Unit: currency of CurrencyID - omitted — Written as 0 (full replacement).

Format: double
IsBillableboolean

Whether the expense is billable to the customer. - omitted — Written as false (full replacement). > Not yet documented: behaviour.

Commentstringnull

Free-text comment. Null: Omitted or null clears the stored comment (full replacement). > Not yet documented: constraints → max length.

Max length: 255
ExchangeRatenumber

Exchange rate from CurrencyID to the owner's legal-entity currency. A suggested value for a date: GET /v2/exchange-rate (GET /v2/exchange-rate) — the call the TimeLog expense form makes. > Not yet documented: constraints → unit.

Format: double
CurrencyIDinteger

Currency of Amount and VatAmount. Refers to CurrencyRead.CurrencyID. Look up valid values with GET /v1/currency/{status}. Valid values come from GET /v1/currency/{status}.

Format: int32
ExpenseTypeIDinteger

The expense type; must belong to the owner's legal entity. Refers to ExpenseTypeRead.ExpenseTypeID. Look up valid values with GET /v1/expense-type/{status}. Valid values come from GET /v1/expense-type/{status}.

Format: int32
PaymentMethodIDinteger

How the employee paid. Refers to PaymentMethodRead.PaymentMethodID. Look up valid values with GET /v1/payment-method/{status}.

Format: int32
ProjectIDinteger

The project the expense is booked on. Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration. - changed — Accepted when ProjectSubContractID is a contract of the NEW project (otherwise 37042). - unchanged — Project must not be Completed (38006). Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.

Format: int32
ProjectSubContractIDinteger

The project contract the expense is booked on. Send the expense's current contract to keep it, or another contract of ProjectID to move it. Refers to ContractRead.ProjectSubContractID. Look up valid values with GET /v1/contract. - omitted or 0 — Written as 0 (non-nullable int, no validator rule); no contract 0 exists on the project → 500 Code 102 / 37042. - not a contract of ProjectID — 37042. - a prepaid-services contract — 31134 (rest.projectmanagement.expense-contract-prepaid). - an inactive (not Active) contract of ProjectID — The active-status rule is skipped on update. Requirement is effective, not declared: the C# property is a plain int and the validator has no rule for it. Valid values come from GET /v1/contract.

Format: int32
Request Preview
Response

Response will appear here after sending the request

Authentication

header
Authorizationstring
Required

Bearer token. OAuth 2.0 bearer token, sent as Authorization: Bearer <token>. JWT and reference tokens are both accepted. The token must carry the required scope (default tlp).

The token's sub claim is the acting user. A token WITHOUT sub (client-credentials) acts as the system service user. If the sub user cannot log in (inactive, locked, deleted), the request is rejected with 401 — it is no longer let through without a user.

Not yet documented: how to obtain a token.

Not yet documented: token lifetime.

Body

application/json
EmployeeExpenseIDinteger

The expense to update (from GET /v2/employee-expense).

Identifier of this object, unique per TimeLog site.

  • ≤ 0 — 400, Code 200.

  • unknown ID — 500, Code 100 (the expense lookup throws a data-access exception).

Datestring

The expense (receipt) date.

Date only — no time component.

  • a time component is sent — Truncated to the date (DateTime.Date).

Must be inside the owner's employment (38022) and not in a closed accounting period (37699).

Not yet documented: constraints → timezone.

Amountnumber

The expense amount, in the currency CurrencyID.

Unit: currency of CurrencyID

  • omitted — Written as 0 (full replacement).

NotNull on a non-nullable double never fails: 0 and negative amounts pass.

VatAmountnumber

The VAT part, in the currency CurrencyID.

Unit: currency of CurrencyID

  • omitted — Written as 0 (full replacement).
IsBillableboolean

Whether the expense is billable to the customer.

  • omitted — Written as false (full replacement).

Not yet documented: behaviour.

Commentstring,null

Free-text comment.

Null: Omitted or null clears the stored comment (full replacement).

Not yet documented: constraints → max length.

ExchangeRatenumber

Exchange rate from CurrencyID to the owner's legal-entity currency.

A suggested value for a date: GET /v2/exchange-rate (GET /v2/exchange-rate) — the call the TimeLog expense form makes.

Not yet documented: constraints → unit.

CurrencyIDinteger

Currency of Amount and VatAmount.

Refers to CurrencyRead.CurrencyID. Look up valid values with GET /v1/currency/{status}.

Valid values come from GET /v1/currency/{status}.

ExpenseTypeIDinteger

The expense type; must belong to the owner's legal entity.

Refers to ExpenseTypeRead.ExpenseTypeID. Look up valid values with GET /v1/expense-type/{status}.

Valid values come from GET /v1/expense-type/{status}.

PaymentMethodIDinteger

How the employee paid.

Refers to PaymentMethodRead.PaymentMethodID. Look up valid values with GET /v1/payment-method/{status}.

ProjectIDinteger

The project the expense is booked on.

Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.

  • changed — Accepted when ProjectSubContractID is a contract of the NEW project (otherwise 37042).

  • unchanged — Project must not be Completed (38006).

Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.

ProjectSubContractIDinteger

The project contract the expense is booked on. Send the expense's current contract to keep it, or another contract of ProjectID to move it.

Refers to ContractRead.ProjectSubContractID. Look up valid values with GET /v1/contract.

  • omitted or 0 — Written as 0 (non-nullable int, no validator rule); no contract 0 exists on the project → 500 Code 102 / 37042.

  • not a contract of ProjectID — 37042.

  • a prepaid-services contract — 31134 (rest.projectmanagement.expense-contract-prepaid).

  • an inactive (not Active) contract of ProjectID — The active-status rule is skipped on update.

Requirement is effective, not declared: the C# property is a plain int and the validator has no rule for it.

Valid values come from GET /v1/contract.

Responses

*Success without content: HTTP 200 with the literal JSON body null. Body is the JSON literal null.

Order: none

Empty result: none*