Submit a timesheet period for approval
Closes every day from StartDate to EndDate of an employee's timesheet and sends it for approval.
The API equivalent of the employee pressing "submit" on a week or month. Refused as a whole when the employee has a time tracker running on one of the days, or when the account forbids closing a period with zero hours / below standard time. Can be called for another employee by a user with the rights described below.
Use it to:
- Auto-submit timesheets at the end of the week for employees whose hours come from another system.
- A manager or integration user closes the month for employees before payroll.
Not for:
- Submitting single registrations — use
POST /v1/approval/timesheets/submit-time-registrations. - Approving or rejecting — no REST operation in this service does that.
Rules across fields:
- R1: StartDate must not be after EndDate; otherwise the day list is empty and the call fails with 500. Not validated.
Behaviour:
- Not idempotent.
- Safe to retry only under a condition. Re-submitting closes nothing new but adds another dialogue entry and raises the event again (no 'already submitted' check on this path).
- Duplicates: Not deduplicated (see safe_to_retry_detail).
- Side effect: Every project-time and absence registration of the employee in the period gets status Closed (employee can no longer edit).
- Side effect: An approval-dialogue entry (CloseTimeTrackingPeriod) with Comment is stored; approval context items are set to Submitted.
- Changes are visible to the next read immediately.
- Batch: all_or_nothing
Call first: GET /v1/approval/timesheets/get-status-by-period
Next: GET /v1/approval/timesheets/get-status-by-period
See also: POST /v1/approval/timesheets/submit-dates, POST /v1/approval/timesheets/submit-time-registrations
Not yet documented: behaviour → batch semantics; behaviour → side effects; examples (success).
curl -X POST "https://app3.timelog.com/your-account/api/v1/approval/timesheets/submit-period" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{
"EmployeeUserID": 123,
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"Comment": "null"
}'
import requests
import json
url = "https://app3.timelog.com/your-account/api/v1/approval/timesheets/submit-period"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
}
data = {
"EmployeeUserID": 123,
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"Comment": "null"
}
response = requests.post(url, headers=headers, json=data)
print(response.json())
const response = await fetch("https://app3.timelog.com/your-account/api/v1/approval/timesheets/submit-period", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_TOKEN"
},
body: JSON.stringify({
"EmployeeUserID": 123,
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"Comment": "null"
})
});
const data = await response.json();
console.log(data);
package main
import (
"fmt"
"net/http"
"bytes"
"encoding/json"
)
func main() {
data := []byte(`{
"EmployeeUserID": 123,
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"Comment": "null"
}`)
req, err := http.NewRequest("POST", "https://app3.timelog.com/your-account/api/v1/approval/timesheets/submit-period", 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/approval/timesheets/submit-period')
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 = '{
"EmployeeUserID": 123,
"StartDate": "2024-12-25T10:00:00Z",
"EndDate": "2024-12-25T10:00:00Z",
"Comment": "null"
}'
response = http.request(request)
puts response.body
"null"
{
"Code": 200,
"Details": [
"'Employee User ID' must be greater than '0'."
],
"Message": "Invalid model state error.",
"Parameters": "model: {"StartDate":"0001-01-01T00:00:00","EndDate":"0001-01-01T00:00:00","Comment":null,"EmployeeUserID":0}",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/approval/timesheets/submit-period"
}
{
"error": "Unauthorized",
"message": "Authentication required. Please provide a valid API token",
"code": 401
}
{
"Code": 100,
"Details": [],
"Message": "Unknown error occur in system, please contact system administrator.",
"Parameters": "model: null",
"Url": "https://app{server-number}.timelog.com/{account}/api/v1/approval/timesheets/submit-period"
}
/v1/approval/timesheets/submit-period
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
User ID of the employee whose period is submitted.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
- 0, negative or omitted — 400 "'Employee User ID' must be greater than '0'."
- another user than the caller, without the needed rights — 500 Code 102, ErrorCode 37966.
First day of the period, inclusive. Date only — no time component. - omitted — Binds to 0001-01-01 (no validation rule); with a real EndDate the period then spans two millennia. - later than EndDate — No validation rule: the day list is empty and the engine fails → 500 Code 100. > Not yet documented: behaviour.
Last day of the period, inclusive. Date only — no time component.
Optional free text stored on the period's submit dialogue entry. Contains personal data.
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
User ID of the employee whose period is submitted.
Refers to UserRead.UserID. Look up valid values with GET /v1/user.
-
0, negative or omitted — 400 "'Employee User ID' must be greater than '0'."
-
another user than the caller, without the needed rights — 500 Code 102, ErrorCode 37966.
First day of the period, inclusive.
Date only — no time component.
-
omitted — Binds to 0001-01-01 (no validation rule); with a real EndDate the period then spans two millennia.
-
later than EndDate — No validation rule: the day list is empty and the engine fails → 500 Code 100.
Not yet documented: behaviour.
Last day of the period, inclusive.
Date only — no time component.
Optional free text stored on the period's submit dialogue entry.
Contains personal data.
Responses
Success without content: HTTP 200 with the literal JSON body null. Literal JSON null (TafSuccessResult). Read the result back with get-status-by-period.