Timesheet-setups
GET List timesheet setups in the workspace
https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups
Returns the timesheet setups configured in the workspace — each one defines the period cadence, the approval chain and the members whose timesheets it generates. Setups whose end date has passed are left out unless include_discontinued is set, and the member and approver filters narrow the list to the setups a given user takes part in. Requires the manage_timesheet_approvals permission.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups \
-H "Content-Type: application/json" \
-u <email>:<password>
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.SetBasicAuth("<email>", "<password>")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
req.basic_auth '<email>', '<password>'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${base64.encode(<email>:<password>)}`
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups', headers={'content-type': 'application/json', 'Authorization' : 'Basic %s' % b64encode(b"<email>:<password>").decode("ascii")})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new().basic_auth("<email>", "<password>");
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
| workspace_id | integer | true | workspace ID |
Query
| name | type | required | description |
|---|---|---|---|
| include_discontinued | boolean | false | include setups whose end_date has passed |
| user_account_id | integer | false | filter to setups containing this member |
| approver_user_account_id | integer | false | filter to setups where this user is an approver |
Response
200
| Name | Type | Description |
|---|---|---|
| items | Array of object | - |
items
| Name | Type | Description |
|---|---|---|
| created_at | string | CreatedAt is the row creation timestamp. |
| deleted_at | string | DeletedAt soft-deletes the setup. While set, the setup is excluded from active queries and no new periods are generated for it. |
| email_reminder_enabled | boolean | EmailReminderEnabled gates the pending-reminder email channel for this setup. When false (the default) the reminder cron skips this setup's members even if ReminderDay/ReminderTime are set. |
| end_date | string | EndDate is set when the setup is discontinued. While nil, the setup continues to generate periods indefinitely. When set, no new periods are generated past this date. |
| id | integer | ID is the surrogate key. |
| period_days | integer | PeriodDays is the length of a period in days. Required (and minimum 1) when Periodicity == PeriodicityDays. Must be nil otherwise. |
| periodicity | string | Periodicity defines the cadence of the timesheet periods. See the Periodicity constants for valid values. Valid values: weekly, monthly, days. |
| reminder_day | integer | ReminderDay defines when in the period a reminder is sent to members who haven't submitted yet. Interpretation depends on Periodicity: - weekly: 0..6 (0 = Sunday) - monthly: 1..31 (clamps to month length) - days: number of days into the period Reminders are not sent when ReminderDay or ReminderTime is nil. |
| reminder_time | string | ReminderTime is the wall-clock time the reminder is sent, formatted HH:MM, interpreted in each member's own timezone (resolved when the reminder cron runs; UTC when the member's timezone is unknown). Reminders are not sent when ReminderDay or ReminderTime is nil. |
| start_date | string | StartDate is the anchor for period computation and the inclusive start of the very first period. All subsequent period boundaries derive from this date plus the periodicity rule. |
| updated_at | string | UpdatedAt tracks the last mutation. Nil for never-updated rows. |
| workspace_id | integer | WorkspaceID is the workspace this setup belongs to. |
400
Bad Request
403
forbidden (requires manage_timesheet_approvals)
500
Internal Server Error
GET Get a timesheet setup
https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}
Returns one timesheet setup by ID, with its period cadence, start and end dates and reminder settings. The approval chain is available from the setup's /approvers endpoint. Requires the manage_timesheet_approvals permission.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id} \
-H "Content-Type: application/json" \
-u <email>:<password>
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.SetBasicAuth("<email>", "<password>")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
req.basic_auth '<email>', '<password>'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${base64.encode(<email>:<password>)}`
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}', headers={'content-type': 'application/json', 'Authorization' : 'Basic %s' % b64encode(b"<email>:<password>").decode("ascii")})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new().basic_auth("<email>", "<password>");
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
| workspace_id | integer | true | workspace ID |
| setup_id | integer | true | setup ID |
Response
200
| Name | Type | Description |
|---|---|---|
| created_at | string | CreatedAt is the row creation timestamp. |
| deleted_at | string | DeletedAt soft-deletes the setup. While set, the setup is excluded from active queries and no new periods are generated for it. |
| email_reminder_enabled | boolean | EmailReminderEnabled gates the pending-reminder email channel for this setup. When false (the default) the reminder cron skips this setup's members even if ReminderDay/ReminderTime are set. |
| end_date | string | EndDate is set when the setup is discontinued. While nil, the setup continues to generate periods indefinitely. When set, no new periods are generated past this date. |
| id | integer | ID is the surrogate key. |
| period_days | integer | PeriodDays is the length of a period in days. Required (and minimum 1) when Periodicity == PeriodicityDays. Must be nil otherwise. |
| periodicity | string | Periodicity defines the cadence of the timesheet periods. See the Periodicity constants for valid values. Valid values: weekly, monthly, days. |
| reminder_day | integer | ReminderDay defines when in the period a reminder is sent to members who haven't submitted yet. Interpretation depends on Periodicity: - weekly: 0..6 (0 = Sunday) - monthly: 1..31 (clamps to month length) - days: number of days into the period Reminders are not sent when ReminderDay or ReminderTime is nil. |
| reminder_time | string | ReminderTime is the wall-clock time the reminder is sent, formatted HH:MM, interpreted in each member's own timezone (resolved when the reminder cron runs; UTC when the member's timezone is unknown). Reminders are not sent when ReminderDay or ReminderTime is nil. |
| start_date | string | StartDate is the anchor for period computation and the inclusive start of the very first period. All subsequent period boundaries derive from this date plus the periodicity rule. |
| updated_at | string | UpdatedAt tracks the last mutation. Nil for never-updated rows. |
| workspace_id | integer | WorkspaceID is the workspace this setup belongs to. |
403
forbidden (requires manage_timesheet_approvals)
404
not found or deleted
500
Internal Server Error
GET List active approvers of a timesheet setup
https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers
Returns the setup's active approvers ordered by layer, which is the chain a submitted timesheet travels through; several approvers may share one layer, in which case any of them can act for it. Requires the manage_timesheet_approvals permission.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers \
-H "Content-Type: application/json" \
-u <email>:<password>
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.SetBasicAuth("<email>", "<password>")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
req.basic_auth '<email>', '<password>'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${base64.encode(<email>:<password>)}`
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers', headers={'content-type': 'application/json', 'Authorization' : 'Basic %s' % b64encode(b"<email>:<password>").decode("ascii")})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new().basic_auth("<email>", "<password>");
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/approvers".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
| workspace_id | integer | true | workspace ID |
| setup_id | integer | true | setup ID |
Response
200
| Name | Type | Description |
|---|---|---|
| items | Array of object | - |
items
| Name | Type | Description |
|---|---|---|
| created_at | string | CreatedAt is the row creation timestamp. |
| deleted_at | string | DeletedAt soft-deletes the approver assignment. While set, the row is treated as if the approver was never assigned. |
| id | integer | ID is the surrogate key. |
| layer | integer | Layer is the 0-indexed tier of this approver in the approval chain. A layer may have multiple approvers (multiple rows sharing the same setup_id and layer); any one of them acting is enough to advance the timesheet to the next layer. |
| setup_id | integer | SetupID references the parent TimesheetSetup. |
| user_account_id | integer | UserAccountID is the approver's account identifier. |
| workspace_id | integer | WorkspaceID is the workspace this approver's setup belongs to. |
400
invalid setup ID
403
forbidden (requires manage_timesheet_approvals)
404
setup not found or deleted
500
Internal Server Error
GET List members of a timesheet setup
https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members
Returns the members assigned to a timesheet setup — the users whose timesheets it generates each period — with their current status. Members who have been discontinued are left out unless include_discontinued is set. Requires the manage_timesheet_approvals permission.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members \
-H "Content-Type: application/json" \
-u <email>:<password>
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.SetBasicAuth("<email>", "<password>")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
req.basic_auth '<email>', '<password>'
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members", {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Basic ${base64.encode(<email>:<password>)}`
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members', headers={'content-type': 'application/json', 'Authorization' : 'Basic %s' % b64encode(b"<email>:<password>").decode("ascii")})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new().basic_auth("<email>", "<password>");
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/timesheet-setups/{setup_id}/members".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
| workspace_id | integer | true | workspace ID |
| setup_id | integer | true | setup ID |
Query
| name | type | required | description |
|---|---|---|---|
| include_discontinued | boolean | false | include discontinued members |
Response
200
| Name | Type | Description |
|---|---|---|
| items | Array of object | - |
items
| Name | Type | Description |
|---|---|---|
| created_at | string | CreatedAt is the row creation timestamp. |
| deleted_at | string | DeletedAt soft-deletes the membership. While set, the row is treated as if it didn't exist for membership checks. |
| discontinued_at | string | DiscontinuedAt marks the member as discontinued: they keep their past timesheets but are excluded from setup resolution, so no new periods are generated for them. Nil means active; reactivation clears it. Independent of DeletedAt (removal). |
| id | integer | ID is the surrogate key. |
| setup_id | integer | SetupID references the parent TimesheetSetup. |
| status | string | Status is the member's lifecycle state derived from DiscontinuedAt. It is populated by the service layer and has no backing DB column. Valid values: active, discontinued. |
| user_account_id | integer | UserAccountID is the member's account identifier. |
| workspace_id | integer | WorkspaceID is the workspace this member's setup belongs to. |
400
invalid setup ID
403
forbidden (requires manage_timesheet_approvals)
404
setup not found or deleted
500
Internal Server Error