Create a mileage registration on a project
Creates a mileage (car travel) registration on a project, for the caller or on behalf of another user.
Registers a trip by car on a project so it is reimbursed to the employee and, when billable, invoiced to the customer at the mileage rate. The contract is the one you send, or the project's default mileage contract. On success the API does not return the registration: it answers 302 with a Location header pointing to GET /v1/mileage-registration/{id}.
Use it to:
- Mobile or expense app posting a trip after the user picked a project from search-for-expense-travel-registration-…
- An assistant or admin system registering mileage for other employees (UserID)
Not for:
- Travel TIME (hours) — use a time registration; the Expenses route POST /v1/mileage-registration can create both together
Rules across fields:
- R1: ProjectSubContractID, when > 0, must belong to ProjectID.
- R2: When ShowOdometer is on and either odometer is sent, OdometerEnd > OdometerStart (400). With OdometerIsRequired also on and both sent, Distance × (IsRoundtrip ? 2 : 1) must equal OdometerEnd − OdometerStart.
- R3: A personal CarID must belong to the owner (UserID or caller).
- R4: MileageRateID must have a rate period covering Date.
Behaviour:
- Not idempotent.
- Not safe to retry blindly. Each call creates a new registration with a new GUID; nothing deduplicates. After a lost response, check the owner's mileage registrations before retrying.
- Duplicates: Not deduplicated (except: one mileage registration per TimeRegistrationID, 37825).
- Side effect: Creates the mileage registration, with an expense number when the auto-number series is on, the travel product number and a generated system text.
- Side effect: Creates mileage financial data and invoicing potential, recalculates the contract's estimated values, and creates the employee reimbursement posting.
- Changes are visible to the next read immediately.
- The insert and the side effects run in one transaction scope; the redirect target reads the new row.
Call first: GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration
See also: POST /v1/project-header/employee-expense
Not yet documented: examples; response → success.
curl -X POST "https://app3.timelog.com/your-account/api/v1/project-header/mileage-registration" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/project-header/mileage-registration"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/project-header/mileage-registration", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/project-header/mileage-registration", bytes.NewBuffer(data))
if err != nil {
panic(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer YOUR_API_TOKEN")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("Response Status:", resp.Status)
}
require 'net/http'
require 'json'
uri = URI('https://app3.timelog.com/your-account/api/v1/project-header/mileage-registration')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer YOUR_API_TOKEN'
request.body = '{
"Date": "2024-12-25T10:00:00Z",
"Purpose": "example_string",
"OdometerStart": "null",
"OdometerEnd": "null",
"Distance": 42,
"StartAddress": "123 Main St",
"EndAddress": "123 Main St",
"ProjectID": 123,
"ProjectSubContractID": 123,
"UserID": "null",
"CarID": 123,
"MileageRateID": 123,
"IsRoundtrip": true,
"TimeRegistrationID": "null",
"IsBillable": true,
"TravelTimeRegistration": {
"TaskID": 123,
"Hours": 3.14,
"Minutes": 42
}
}'
response = http.request(request)
puts response.body
{}
{
"error": "Bad Request",
"message": "The request contains invalid parameters or malformed data",
"code": 400,
"details": [
{
"field": "email",
"message": "Invalid email format"
}
]
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"error": "Forbidden",
"message": "You don't have permission to access this resource",
"code": 403
}
{
"error": "Internal Server Error",
"message": "An unexpected error occurred on the server",
"code": 500,
"requestId": "req_1234567890"
}
/v1/project-header/mileage-registration
Target server for requests. Edit to use your own host.
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.
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.The media type of the request body
The date of the trip. Time zone: not yet documented. Mapped as sent (the adapter does not truncate a time component, unlike the expense adapter). Must have a mileage-rate period covering it (37845) and must not be in a closed accounting period (37827). > Not yet documented: constraints → timezone.
Why the trip was made. Becomes part of the registration; not part of the generated system text. - empty or whitespace — Not checked by the 400 validator; the domain object raises 60107 (HTTP 500, Code 102).
Odometer reading at the start of the trip. > Not yet documented: constraints → unit. > Not yet documented: requirement.
Odometer reading at the end of the trip. - both odometers sent, ShowOdometer and OdometerIsRequired on, and Distance (×2 if IsRoundtrip) ≠ OdometerEnd − OdometerStart — 60100 (HTTP 500, Code 102). Distance is never derived from the odometers. > Not yet documented: constraints → unit.
One-way distance of the trip. The stored total distance is Distance, doubled when IsRoundtrip is true. Range: min 1. > Not yet documented: constraints → unit.
Where the trip started (free text).
Where the trip ended (free text).
The project the trip is registered on.
Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
No check that the project is active. No check that the owner is allocated to it.
Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
The project contract the mileage is booked on.
Refers to ContractRead.ProjectSubContractID.
- omitted or 0 — The project's default-for-expenses contract, else its main contract; prepaid-services contracts are skipped. None → 37854.
- > 0 — Must be a contract of ProjectID (37042), not prepaid services (37847) and not closed (37846).
> Not yet documented: values → lookup op.
The user the mileage 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.
The car used. A personal car must belong to the registration's owner; a shared car (no employee) is accepted.
Refers to CarRead.CarID. Look up valid values with GET /v1/car.
Valid values come from GET /v1/car.
The mileage rate the distance is reimbursed and invoiced at. Must have a rate period covering Date.
Refers to MileageRateRead.MileageRateID. Look up valid values with GET /v1/mileage-rate.
true = the trip was there and back; the stored total distance is Distance × 2.
Optional link to a time registration (for example the travel time of the same trip).
Refers to TimeRegistrationRead.TimeRegistrationID.
- another mileage registration already links to it — 37825 (HTTP 500, Code 102).
No check that the time registration exists or belongs to the same owner or project.
Whether the mileage is billable to the customer. - account features Billable AND BillableExpense are on — applied - either feature is off — Replaced by the resolved contract's IsMileageBillable. > Not yet documented: default.
Optional travel time to register together with the mileage (POST /v1/mileage-registration only). - sent to POST /v1/mileage-registration — A TravelTime time registration is created first (owner, Date, Purpose as comment) and linked via TimeRegistrationID; it is deleted again if the mileage save fails. TimeRegistrationID in the body is then overwritten. - sent to POST /v1/project-header/mileage-registration — That route binds MileageRegistrationApiCreateModelForProject, which has no such property.
Request Preview
Response
Response will appear here after sending the request
Authentication
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
The date of the trip.
Time zone: not yet documented.
Mapped as sent (the adapter does not truncate a time component, unlike the expense adapter). Must have a mileage-rate period covering it (37845) and must not be in a closed accounting period (37827).
Not yet documented: constraints → timezone.
Why the trip was made. Becomes part of the registration; not part of the generated system text.
- empty or whitespace — Not checked by the 400 validator; the domain object raises 60107 (HTTP 500, Code 102).
Odometer reading at the start of the trip.
Not yet documented: constraints → unit.
Not yet documented: requirement.
Odometer reading at the end of the trip.
- both odometers sent, ShowOdometer and OdometerIsRequired on, and Distance (×2 if IsRoundtrip) ≠ OdometerEnd − OdometerStart — 60100 (HTTP 500, Code 102). Distance is never derived from the odometers.
Not yet documented: constraints → unit.
One-way distance of the trip. The stored total distance is Distance, doubled when IsRoundtrip is true.
Range: min 1.
Not yet documented: constraints → unit.
Where the trip started (free text).
Where the trip ended (free text).
The project the trip is registered on.
Refers to ProjectHeaderRead.ProjectID. Look up valid values with GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
No check that the project is active. No check that the owner is allocated to it.
Valid values come from GET /v1/project-header/search-for-expense-travel-registration-order-by-recent-registration.
The project contract the mileage is booked on.
Refers to ContractRead.ProjectSubContractID.
-
omitted or 0 — The project's default-for-expenses contract, else its main contract; prepaid-services contracts are skipped. None → 37854.
-
0 — Must be a contract of ProjectID (37042), not prepaid services (37847) and not closed (37846).
Not yet documented: values → lookup op.
The user the mileage 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.
The car used. A personal car must belong to the registration's owner; a shared car (no employee) is accepted.
Refers to CarRead.CarID. Look up valid values with GET /v1/car.
Valid values come from GET /v1/car.
The mileage rate the distance is reimbursed and invoiced at. Must have a rate period covering Date.
Refers to MileageRateRead.MileageRateID. Look up valid values with GET /v1/mileage-rate.
true = the trip was there and back; the stored total distance is Distance × 2.
Optional link to a time registration (for example the travel time of the same trip).
Refers to TimeRegistrationRead.TimeRegistrationID.
- another mileage registration already links to it — 37825 (HTTP 500, Code 102).
No check that the time registration exists or belongs to the same owner or project.
Whether the mileage is billable to the customer.
-
account features Billable AND BillableExpense are on — applied
-
either feature is off — Replaced by the resolved contract's IsMileageBillable.
Not yet documented: default.
Optional travel time to register together with the mileage (POST /v1/mileage-registration only).
-
sent to POST /v1/mileage-registration — A TravelTime time registration is created first (owner, Date, Purpose as comment) and linked via TimeRegistrationID; it is deleted again if the mileage save fails. TimeRegistrationID in the body is then overwritten.
-
sent to POST /v1/project-header/mileage-registration — That route binds MileageRegistrationApiCreateModelForProject, which has no such property.