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

Not yet documented: examples; response → success.

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. > 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
Datestring

The date of the expense (receipt date). Time zone: not yet documented. - 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). > Not yet documented: constraints → timezone.

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. > Not yet documented: behaviour.

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. > Not yet documented: description.

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). > Not yet documented: default.

Commentstring

Free-text comment. > Not yet documented: constraints → max length.

Max length: 255
ExchangeRatenumber

Exchange rate from CurrencyID to the owner's legal-entity currency. > 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}. > Not yet documented: behaviour.

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. > Not yet documented: values → lookup op.

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). > Not yet documented: values → lookup op. > Not yet documented: behaviour.

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. > Not yet documented: values → lookup op.

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
Datestring

The date of the expense (receipt date).

Time zone: not yet documented.

  • 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).

Not yet documented: constraints → timezone.

Numberstring

Expense number. Accepted in the body but NOT used.

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

Not yet documented: behaviour.

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.

Not yet documented: description.

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).

Not yet documented: default.

Commentstring

Free-text comment.

Not yet documented: constraints → max length.

ExchangeRatenumber

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

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}.

Not yet documented: behaviour.

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.

Not yet documented: values → lookup op.

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).

Not yet documented: values → lookup op.

Not yet documented: behaviour.

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.

Not yet documented: values → lookup op.

Responses