ProjectsCreate an employee expense on a project

Create an employee expense on a project

Creates an employee expense (out-of-pocket cost to be reimbursed) on a project, for the caller or on behalf of another user.

Registers an expense an employee paid — amount, VAT, currency, expense type, payment method — on a project, optionally billable to its customer. The contract is the one you send, or the project's default expense contract. On success the API does not return the expense: it answers 302 with a Location header pointing to GET /v1/employee-expense/{id}. Receipts cannot be attached here.

Use it to:

  • Expense app submitting a receipt line after the user picked a project from search-for-expense-travel-registration-…

Not for:

  • Expenses paid by the company on a project (project expenses) — a different Expenses operation

Rules across fields:

  • R1: ProjectSubContractID, when > 0, must belong to ProjectID, be Active and not be prepaid services.
  • R2: ExpenseTypeID must belong to the owner's (UserID or caller) legal entity.
  • R3: IsBillable must be true when the resolved contract is fixed-price.
  • R4: Date must be inside the owner's employment and not in a closed accounting period.

Behaviour:

  • Not idempotent.
  • Not safe to retry blindly. Each call creates a new expense with a new GUID; nothing deduplicates. After a lost response, check the owner's expenses before retrying.
  • Duplicates: Not deduplicated.
  • Side effect: Creates the employee expense with the currency conversion rates to the project's and the owner's legal-entity currencies.
  • Side effect: Writes registration currency data for the expense.
  • On failure: No transaction scope wraps the insert and the follow-up writes. A failure after the insert — e.g. in the currency-data notification or the read-back — returns 500 while the expense row stays.
  • Changes are visible to the next read immediately.

Call first: GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration, GET /v1/currency/{status}

See also: POST /v1/project-header/mileage-registration

curl -X POST "https://app3.timelog.com/your-account/api/v1/project-header/employee-expense" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
  "Date": "2024-12-25T10:00:00Z",
  "Number": "example_string",
  "Amount": 3.14,
  "VatAmount": 3.14,
  "IsBillable": true,
  "Comment": "example_string",
  "ExchangeRate": 3.14,
  "CurrencyID": 123,
  "ExpenseTypeID": 123,
  "PaymentMethodID": 123,
  "ProjectID": 123,
  "ProjectSubContractID": 123,
  "UserID": "null"
}'
{}
POST
/v1/project-header/employee-expense
POST
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.

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.
Content-Typestring
Required

The media type of the request body

Options: application/json
Datestring

The date of the expense (receipt date). - a time component is sent — Truncated to the date (DateTime.Date) by the adapter. Must be inside the owner's employment (38022) and not in a closed accounting period (37699).

Format: date-time
Numberstring

Expense number. Accepted in the body but NOT used. - always — The adapter sets the number to an empty string whatever is sent.

Amountnumber

The expense amount, in the currency CurrencyID. Unit: currency of CurrencyID The validator's NotNull rule cannot fail on a non-nullable number: 0 and negative amounts are accepted.

Format: double
VatAmountnumber

The VAT part of the expense, in the currency CurrencyID. Unit: currency of CurrencyID

Format: double
IsBillableboolean

Whether the expense is billable to the customer. - false on a fixed-price contract — 31140 (HTTP 500, Code 102).

Commentstring

Free-text comment.

Max length: 255
ExchangeRatenumber

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

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. Intended to belong to the owner's legal entity, but that rule is not enforced. Refers to ExpenseTypeRead.ExpenseTypeID. - unknown ID — 37019. - type of another legal entity than the owner's — Accepted — ExpenseTypeIsValid sets 37998 but returns true, so the violation is never added.

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. Must exist and be active. Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration. 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. Refers to ContractRead.ProjectSubContractID. - omitted or 0 — The project's default-for-expenses contract, else its main contract. If that contract is prepaid services the value stays 0 and the save fails a contract rule. - > 0 — Must be a contract of ProjectID (37042), Active (31132) and not prepaid services (31134).

Format: int32
UserIDintegernull

The user the expense is registered for. Omit (or send your own UserID) to register for yourself. Refers to UserRead.UserID. Default: the caller - ≤ 0 — 400, Code 200.

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.

Body

application/json
Datestring

The date of the expense (receipt date).

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

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

Numberstring

Expense number. Accepted in the body but NOT used.

  • always — The adapter sets the number to an empty string whatever is sent.
Amountnumber

The expense amount, in the currency CurrencyID.

Unit: currency of CurrencyID

The validator's NotNull rule cannot fail on a non-nullable number: 0 and negative amounts are accepted.

VatAmountnumber

The VAT part of the expense, in the currency CurrencyID.

Unit: currency of CurrencyID

IsBillableboolean

Whether the expense is billable to the customer.

  • false on a fixed-price contract — 31140 (HTTP 500, Code 102).
Commentstring

Free-text comment.

ExchangeRatenumber

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

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. Intended to belong to the owner's legal entity, but that rule is not enforced.

Refers to ExpenseTypeRead.ExpenseTypeID.

  • unknown ID — 37019.

  • type of another legal entity than the owner's — Accepted — ExpenseTypeIsValid sets 37998 but returns true, so the violation is never added.

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. Must exist and be active.

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

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.

Refers to ContractRead.ProjectSubContractID.

  • omitted or 0 — The project's default-for-expenses contract, else its main contract. If that contract is prepaid services the value stays 0 and the save fails a contract rule.

  • 0 — Must be a contract of ProjectID (37042), Active (31132) and not prepaid services (31134).

UserIDinteger,null

The user the expense is registered for. Omit (or send your own UserID) to register for yourself.

Refers to UserRead.UserID.

Default: the caller

  • ≤ 0 — 400, Code 200.

Responses