diff --git a/Cargo.lock b/Cargo.lock index 9d8df91..0e76b07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,6 +245,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "console" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d64e8af5551369d19cf50138de61f1c42074ab970f74e99be916646777f8fc87" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "const-oid" version = "0.10.2" @@ -324,12 +335,34 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -541,13 +574,16 @@ dependencies = [ "clap", "dirs", "dotenvy", + "insta", "mockito", "open", "rand 0.10.1", "reqwest", + "rpassword", "serde", "serde_json", "sha2", + "tempfile", "tiny_http", "toml", "url", @@ -769,6 +805,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "insta" +version = "1.47.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a6248eb93a4401ed2f37dfe8ea592d3cf05b7cf4f8efa867b6895af7e094e" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -847,6 +895,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + [[package]] name = "litemap" version = "0.8.1" @@ -1263,12 +1317,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rpassword" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d4c8b64f049c6721ec8ccec37ddfc3d641c4a7fca57e8f2a89de509c73df39" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.59.0", +] + +[[package]] +name = "rtoolbox" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "rustls" version = "0.23.37" @@ -1486,6 +1574,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -1961,6 +2062,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index ad75bb2..3473653 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,9 +25,12 @@ sha2 = "0.11" chrono = { version = "0.4", features = ["serde"] } url = "2" dotenvy = "0.15" +tempfile = "3" +rpassword = "7" [dev-dependencies] mockito = "1" +insta = "1" [profile.release] opt-level = "z" diff --git a/README.md b/README.md index 62a2cf7..890329d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,8 @@ hubstaff config setup-oauth This will prompt you for a Client ID and Client Secret. To get those: 1. Go to [developer.hubstaff.com](https://developer.hubstaff.com) > OAuth Apps > Create -2. Set the redirect URI to `http://localhost:19876/callback` +2. Set the redirect URI to `http://127.0.0.1:19876/callback` + This must match exactly, and port `19876` must be free when you run `hubstaff login`. 3. Copy the Client ID and Client Secret Then authenticate: @@ -127,6 +128,9 @@ hubstaff config set auth_url URL hubstaff config set schema_url URL hubstaff config set token TOKEN hubstaff config set format compact +hubstaff config unset organization +hubstaff config unset schema_url +hubstaff config reset hubstaff config set-pat TOKEN hubstaff config setup-oauth hubstaff config show @@ -154,7 +158,7 @@ On macOS this is typically: - `~/Library/Application Support/hubstaff/schema/v2/docs.json` - `~/Library/Application Support/hubstaff/schema/v2/meta.toml` -## Staging / Custom Environments +## Custom Environment ```bash hubstaff config set api_url diff --git a/justfile b/justfile index 207c811..c4c19a4 100644 --- a/justfile +++ b/justfile @@ -34,3 +34,7 @@ install-tools: command -v cargo-deny >/dev/null || cargo install cargo-deny --locked command -v cargo-audit >/dev/null || cargo install cargo-audit --locked command -v cargo-auditable >/dev/null || cargo install cargo-auditable --locked + +refresh-schema-fixture: + curl -sSf https://api.hubstaff.com/v2/docs -o tests/fixtures/schema.json + INSTA_UPDATE=auto cargo test schema_command_table_snapshot -- --nocapture diff --git a/src/api.rs b/src/api.rs index e65195c..06c38fd 100644 --- a/src/api.rs +++ b/src/api.rs @@ -11,6 +11,7 @@ pub fn run_dynamic( schema: &ApiSchema, args: &[String], pretty_json: bool, + organization_override: Option, ) -> Result<(), CliError> { let index = CommandIndex::load_or_build(schema)?; let parsed = ParsedInvocation::parse(args)?; @@ -63,7 +64,13 @@ pub fn run_dynamic( "query options", )?; - fill_defaults_from_config(operation, client, &mut path_values, &mut query_values)?; + fill_defaults_from_config( + operation, + client, + organization_override, + &mut path_values, + &mut query_values, + )?; validate_required_parameters(operation, &path_values, &query_values)?; validate_enum_values(operation, &path_values, ParameterLocation::Path)?; validate_enum_values(operation, &query_values, ParameterLocation::Query)?; @@ -632,6 +639,7 @@ fn validate_known_values( fn fill_defaults_from_config( operation: &Operation, client: &HubstaffClient, + organization_override: Option, path_values: &mut HashMap, query_values: &mut HashMap, ) -> Result<(), CliError> { @@ -654,7 +662,9 @@ fn fill_defaults_from_config( continue; } - let organization_id = client.resolve_organization(None)?.to_string(); + let organization_id = client + .resolve_organization(organization_override)? + .to_string(); match parameter.location { ParameterLocation::Path => { path_values.insert("organization_id".to_string(), organization_id); diff --git a/src/auth.rs b/src/auth.rs index 5345d4c..3715f54 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -7,8 +7,8 @@ use sha2::{Digest, Sha256}; use std::time::Duration; use url::Url; -const PORT_START: u16 = 19876; -const PORT_END: u16 = 19886; +const CALLBACK_PORT: u16 = 19876; +const CALLBACK_REDIRECT_URI: &str = "http://127.0.0.1:19876/callback"; const CALLBACK_TIMEOUT_SECS: u64 = 120; const AUTH_HTTP_TIMEOUT_SECS: u64 = 10; const REFRESH_NETWORK_ERROR: &str = @@ -23,7 +23,7 @@ fn client_id() -> Result { \n\ OAuth login requires a Hubstaff OAuth app. To set up:\n\ 1. Go to https://developer.hubstaff.com > OAuth Apps > Create\n\ - 2. Set redirect URI to http://localhost:19876/callback\n\ + 2. Set redirect URI to http://127.0.0.1:19876/callback\n\ 3. Copy the Client ID and Client Secret\n\ 4. Run: hubstaff config setup-oauth\n\ \n\ @@ -56,10 +56,10 @@ pub fn login() -> Result<(), CliError> { let (verifier, challenge) = generate_pkce(); let state = generate_state(); - let (server, port) = start_callback_server()?; - let redirect_uri = format!("http://localhost:{port}/callback"); + let server = start_callback_server()?; + let redirect_uri = CALLBACK_REDIRECT_URI.to_string(); - let auth_url = build_auth_url(auth_base, &id, &challenge, &redirect_uri, &state); + let auth_url = build_auth_url(auth_base, &id, &challenge, &redirect_uri, &state)?; println!("Opening browser for authentication..."); println!("If the browser doesn't open, visit:\n{auth_url}"); @@ -72,9 +72,7 @@ pub fn login() -> Result<(), CliError> { let tokens = exchange_code(auth_base, &id, &secret, &code, &verifier, &redirect_uri)?; let mut config = Config::load()?; - config.auth.access_token = Some(tokens.access_token); - config.auth.refresh_token = Some(tokens.refresh_token); - config.auth.expires_at = Some(tokens.expires_at); + config.store_tokens(tokens); config.save()?; println!("Authentication successful. Tokens saved."); @@ -129,30 +127,39 @@ fn refresh_token_with_credentials( .json() .map_err(|_| CliError::Auth(REFRESH_AUTH_ERROR.to_string()))?; - let new_access = body["access_token"] - .as_str() - .ok_or_else(|| CliError::Auth(REFRESH_AUTH_ERROR.to_string()))? - .to_string(); - let new_refresh = body["refresh_token"] - .as_str() - .ok_or_else(|| CliError::Auth(REFRESH_AUTH_ERROR.to_string()))? - .to_string(); - - config.auth.access_token = Some(new_access); - config.auth.refresh_token = Some(new_refresh); - if let Some(expires_in) = body["expires_in"].as_i64() { - let expires_at = chrono::Utc::now() + chrono::Duration::seconds(expires_in); - config.auth.expires_at = Some(expires_at.to_rfc3339()); - } + let tokens = + TokenSet::from_json(&body).map_err(|_| CliError::Auth(REFRESH_AUTH_ERROR.to_string()))?; + config.store_tokens(tokens); config.save()?; Ok(()) } -struct TokenResponse { - access_token: String, - refresh_token: String, - expires_at: String, +pub struct TokenSet { + pub access_token: String, + pub refresh_token: String, + pub expires_at: Option, +} + +impl TokenSet { + pub fn from_json(body: &serde_json::Value) -> Result { + let access_token = body["access_token"] + .as_str() + .ok_or_else(|| CliError::Auth("missing access_token in response".into()))? + .to_string(); + let refresh_token = body["refresh_token"] + .as_str() + .ok_or_else(|| CliError::Auth("missing refresh_token in response".into()))? + .to_string(); + let expires_at = body["expires_in"] + .as_i64() + .map(|secs| (chrono::Utc::now() + chrono::Duration::seconds(secs)).to_rfc3339()); + Ok(Self { + access_token, + refresh_token, + expires_at, + }) + } } fn generate_pkce() -> (String, String) { @@ -185,9 +192,10 @@ fn build_auth_url( challenge: &str, redirect_uri: &str, state: &str, -) -> String { +) -> Result { let nonce = generate_nonce(); - let mut url = Url::parse(&format!("{auth_base}/authorizations/new")).unwrap(); + let mut url = Url::parse(&format!("{auth_base}/authorizations/new")) + .map_err(|e| CliError::Config(format!("invalid auth_url '{auth_base}': {e}")))?; url.query_pairs_mut() .append_pair("response_type", "code") .append_pair("client_id", client_id) @@ -197,18 +205,15 @@ fn build_auth_url( .append_pair("code_challenge_method", "S256") .append_pair("state", state) .append_pair("nonce", &nonce); - url.to_string() + Ok(url.to_string()) } -fn start_callback_server() -> Result<(tiny_http::Server, u16), CliError> { - for port in PORT_START..=PORT_END { - if let Ok(server) = tiny_http::Server::http(format!("127.0.0.1:{port}")) { - return Ok((server, port)); - } - } - Err(CliError::Auth(format!( - "could not bind to any port in range {PORT_START}-{PORT_END}" - ))) +fn start_callback_server() -> Result { + tiny_http::Server::http(format!("127.0.0.1:{CALLBACK_PORT}")).map_err(|_| { + CliError::Auth(format!( + "could not bind to 127.0.0.1:{CALLBACK_PORT}. Free that port and try again. Your Hubstaff OAuth app redirect URI must be set to {CALLBACK_REDIRECT_URI}" + )) + }) } fn wait_for_callback(server: &tiny_http::Server, expected_state: &str) -> Result { @@ -296,7 +301,7 @@ fn exchange_code( code: &str, verifier: &str, redirect_uri: &str, -) -> Result { +) -> Result { let client = reqwest::blocking::Client::new(); let resp = client .post(format!("{auth_base}/access_tokens")) @@ -322,28 +327,7 @@ fn exchange_code( .json() .map_err(|e| CliError::Auth(format!("failed to parse token response: {e}")))?; - let access_token = body["access_token"] - .as_str() - .ok_or_else(|| CliError::Auth("missing access_token in response".to_string()))? - .to_string(); - - let refresh_token = body["refresh_token"] - .as_str() - .ok_or_else(|| CliError::Auth("missing refresh_token in response".to_string()))? - .to_string(); - - let expires_at = if let Some(expires_in) = body["expires_in"].as_i64() { - let at = chrono::Utc::now() + chrono::Duration::seconds(expires_in); - at.to_rfc3339() - } else { - String::new() - }; - - Ok(TokenResponse { - access_token, - refresh_token, - expires_at, - }) + TokenSet::from_json(&body) } #[cfg(test)] @@ -437,9 +421,10 @@ mod tests { "https://account.hubstaff.com", "test_client_id", "test_challenge", - "http://localhost:19876/callback", + CALLBACK_REDIRECT_URI, "test_state", - ); + ) + .unwrap(); assert!(url.contains("response_type=code")); assert!(url.contains("client_id=test_client_id")); assert!(url.contains("redirect_uri=")); @@ -456,12 +441,20 @@ mod tests { "https://account.staging.hbstf.co", "test_id", "ch", - "http://localhost:19876/callback", + CALLBACK_REDIRECT_URI, "st", - ); + ) + .unwrap(); assert!(url.starts_with("https://account.staging.hbstf.co/authorizations/new")); } + #[test] + fn auth_url_errors_on_malformed_base() { + let err = + build_auth_url("not a url", "test_id", "ch", CALLBACK_REDIRECT_URI, "st").unwrap_err(); + assert!(matches!(err, CliError::Config(_))); + } + #[test] fn password_generation_length() { let pw = generate_password(); @@ -490,8 +483,7 @@ mod tests { fn callback_server_binds_to_port() { let result = start_callback_server(); assert!(result.is_ok()); - let (_, port) = result.unwrap(); - assert!((PORT_START..=PORT_END).contains(&port)); + drop(result.unwrap()); } #[test] diff --git a/src/command_index.rs b/src/command_index.rs index 9a08680..a728a86 100644 --- a/src/command_index.rs +++ b/src/command_index.rs @@ -1,11 +1,11 @@ use crate::config::Config; use crate::error::CliError; +use crate::persistence::write_atomic; use crate::schema::{ApiSchema, Operation}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, BTreeSet}; use std::fs; -use std::path::Path; const INDEX_VERSION: u32 = 1; @@ -414,20 +414,6 @@ fn write_cache(cache: &CommandIndexCache) -> Result<(), CliError> { write_atomic(&Config::schema_command_index_path(), &content) } -fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), CliError> { - let temp_path = path.with_extension("tmp"); - fs::write(&temp_path, bytes)?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - fs::set_permissions(&temp_path, fs::Permissions::from_mode(0o600))?; - } - - fs::rename(&temp_path, path)?; - Ok(()) -} - #[cfg(test)] mod tests { use super::*; @@ -460,6 +446,37 @@ mod tests { assert_eq!(entry.visible_path_params, vec!["team_id"]); } + // Snapshot of the full command table built from the committed live schema. + // Refresh via `just refresh-schema-fixture` when the API evolves; the diff + // on this snapshot is the review surface for command-shape drift. + #[test] + fn schema_command_table_snapshot() { + use crate::schema::{ApiSchema, SchemaSource}; + use serde_json::Value; + + let raw = include_str!("../tests/fixtures/schema.json"); + let value: Value = serde_json::from_str(raw).expect("fixture is valid JSON"); + let schema = ApiSchema::from_schema(&value, SchemaSource::Cache, None) + .expect("fixture parses into ApiSchema"); + + let entries = build_entries(&schema); + let mut lines = entries + .iter() + .map(|entry| { + format!( + "{:6} {:60} -> {}", + entry.method, + entry.path_template, + usage_line(entry) + ) + }) + .collect::>(); + lines.sort(); + let table = lines.join("\n"); + + insta::assert_snapshot!(table); + } + fn entry(words: &[&str], ids: &[&str], method: &str, op_id: &str) -> CommandEntry { CommandEntry { operation_id: op_id.to_string(), diff --git a/src/config.rs b/src/config.rs index aaf547e..92b2497 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,6 @@ +use crate::auth::TokenSet; use crate::error::CliError; +use crate::persistence::write_atomic; use serde::{Deserialize, Serialize}; use std::fs; use std::path::PathBuf; @@ -8,33 +10,29 @@ const DEFAULT_AUTH_URL: &str = "https://account.hubstaff.com"; const DEFAULT_FORMAT: &str = "compact"; #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(default)] pub struct Config { - #[serde(default = "default_api_url")] pub api_url: String, - #[serde( - default = "default_auth_url", - skip_serializing_if = "is_default_auth_url" - )] + #[serde(skip_serializing_if = "is_default_auth_url")] pub auth_url: String, - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub organization: Option, /// Explicit schema docs URL override. When unset, derived from `api_url`. - #[serde(default, skip_serializing_if = "Option::is_none")] + #[serde(skip_serializing_if = "Option::is_none")] pub schema_url: Option, - #[serde(default = "default_format")] pub format: String, - #[serde(default, skip_serializing_if = "AuthConfig::is_empty")] + #[serde(skip_serializing_if = "AuthConfig::is_empty")] pub auth: AuthConfig, } impl Default for Config { fn default() -> Self { Self { - api_url: default_api_url(), - auth_url: default_auth_url(), + api_url: DEFAULT_API_URL.to_string(), + auth_url: DEFAULT_AUTH_URL.to_string(), organization: None, schema_url: None, - format: default_format(), + format: DEFAULT_FORMAT.to_string(), auth: AuthConfig::default(), } } @@ -56,22 +54,10 @@ impl AuthConfig { } } -fn default_api_url() -> String { - DEFAULT_API_URL.to_string() -} - -fn default_auth_url() -> String { - DEFAULT_AUTH_URL.to_string() -} - fn is_default_auth_url(url: &str) -> bool { url == DEFAULT_AUTH_URL } -fn default_format() -> String { - DEFAULT_FORMAT.to_string() -} - impl Config { pub fn config_dir() -> PathBuf { if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { @@ -116,30 +102,31 @@ impl Config { Ok(config) } - pub fn save(&self) -> Result<(), CliError> { + pub fn ensure_dir() -> Result { let dir = Self::config_dir(); - if !dir.exists() { - fs::create_dir_all(&dir)?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?; - } - } - - let path = Self::config_path(); - let content = toml::to_string_pretty(self)?; - fs::write(&path, &content)?; - + fs::create_dir_all(&dir)?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?; + fs::set_permissions(&dir, fs::Permissions::from_mode(0o700))?; } + Ok(dir) + } + pub fn save(&self) -> Result<(), CliError> { + Self::ensure_dir()?; + let path = Self::config_path(); + let content = toml::to_string_pretty(self)?; + write_atomic(&path, content.as_bytes())?; Ok(()) } + pub fn store_tokens(&mut self, tokens: TokenSet) { + self.auth.access_token = Some(tokens.access_token); + self.auth.refresh_token = Some(tokens.refresh_token); + self.auth.expires_at = tokens.expires_at; + } + pub fn get_token(&self) -> Option { // Environment token precedence is handled by HubstaffClient. self.auth.access_token.clone() @@ -159,6 +146,35 @@ impl Config { .clone() .unwrap_or_else(|| format!("{}/docs", self.api_url.trim_end_matches('/'))) } + + pub fn unset(&mut self, key: &str) -> Result<(), CliError> { + match key { + "organization" => self.organization = None, + "schema_url" => self.schema_url = None, + "api_url" => self.api_url = DEFAULT_API_URL.to_string(), + "auth_url" => self.auth_url = DEFAULT_AUTH_URL.to_string(), + "format" => self.format = DEFAULT_FORMAT.to_string(), + "token" | "refresh_token" => { + return Err(CliError::Config( + "cannot unset auth tokens here; run 'hubstaff logout'".to_string(), + )); + } + _ => { + return Err(CliError::Config(format!( + "unknown config key: {key}. Valid keys: organization, api_url, auth_url, schema_url, format" + ))); + } + } + Ok(()) + } + + pub fn reset(&mut self) { + let auth = std::mem::take(&mut self.auth); + *self = Config { + auth, + ..Config::default() + }; + } } #[cfg(test)] @@ -208,6 +224,47 @@ mod tests { assert!(!auth.is_empty()); } + #[test] + fn store_tokens_overwrites_access_and_refresh() { + let mut config = Config { + auth: AuthConfig { + access_token: Some("old_access".into()), + refresh_token: Some("old_refresh".into()), + expires_at: Some("2025-01-01T00:00:00Z".into()), + }, + ..Default::default() + }; + config.store_tokens(TokenSet { + access_token: "new_access".into(), + refresh_token: "new_refresh".into(), + expires_at: Some("2030-01-01T00:00:00Z".into()), + }); + assert_eq!(config.auth.access_token.as_deref(), Some("new_access")); + assert_eq!(config.auth.refresh_token.as_deref(), Some("new_refresh")); + assert_eq!( + config.auth.expires_at.as_deref(), + Some("2030-01-01T00:00:00Z") + ); + } + + #[test] + fn store_tokens_clears_stale_expires_at_when_new_has_none() { + let mut config = Config { + auth: AuthConfig { + access_token: Some("old_access".into()), + refresh_token: Some("old_refresh".into()), + expires_at: Some("2025-01-01T00:00:00Z".into()), + }, + ..Default::default() + }; + config.store_tokens(TokenSet { + access_token: "new_access".into(), + refresh_token: "new_refresh".into(), + expires_at: None, + }); + assert_eq!(config.auth.expires_at, None); + } + #[test] fn config_serialization_skips_empty_auth() { let config = Config::default(); diff --git a/src/config_commands.rs b/src/config_commands.rs index 908f9a0..0ad79f2 100644 --- a/src/config_commands.rs +++ b/src/config_commands.rs @@ -1,5 +1,7 @@ +use crate::auth::TokenSet; use crate::config::Config; use crate::error::CliError; +use crate::persistence::write_atomic; pub fn set(key: &str, value: &str) -> Result<(), CliError> { let mut config = Config::load()?; @@ -22,6 +24,8 @@ pub fn set(key: &str, value: &str) -> Result<(), CliError> { } "token" => { config.auth.access_token = Some(value.to_string()); + config.auth.refresh_token = None; + config.auth.expires_at = None; } "format" => { if value != "compact" && value != "json" { @@ -44,6 +48,22 @@ pub fn set(key: &str, value: &str) -> Result<(), CliError> { Ok(()) } +pub fn unset(key: &str) -> Result<(), CliError> { + let mut config = Config::load()?; + config.unset(key)?; + config.save()?; + println!("unset {key}"); + Ok(()) +} + +pub fn reset() -> Result<(), CliError> { + let mut config = Config::load()?; + config.reset(); + config.save()?; + println!("Config reset to defaults. Auth tokens left intact (use 'hubstaff logout' to clear)."); + Ok(()) +} + /// Exchange a personal access token (which is a refresh token) for access + refresh tokens. pub fn set_pat(pat: &str) -> Result<(), CliError> { let config = Config::load()?; @@ -68,21 +88,10 @@ pub fn set_pat(pat: &str) -> Result<(), CliError> { .json() .map_err(|e| CliError::Auth(format!("failed to parse token response: {e}")))?; - let access_token = body["access_token"] - .as_str() - .ok_or_else(|| CliError::Auth("missing access_token in response".to_string()))?; - - let refresh_token = body["refresh_token"] - .as_str() - .ok_or_else(|| CliError::Auth("missing refresh_token in response".to_string()))?; + let tokens = TokenSet::from_json(&body)?; let mut config = Config::load()?; - config.auth.access_token = Some(access_token.to_string()); - config.auth.refresh_token = Some(refresh_token.to_string()); - if let Some(expires_in) = body["expires_in"].as_i64() { - let expires_at = chrono::Utc::now() + chrono::Duration::seconds(expires_in); - config.auth.expires_at = Some(expires_at.to_rfc3339()); - } + config.store_tokens(tokens); config.save()?; println!("Authenticated. Access token saved (expires in ~24h, auto-refreshes)."); @@ -98,7 +107,10 @@ pub fn setup_oauth() -> Result<(), CliError> { println!(); println!("1. Go to https://developer.hubstaff.com"); println!("2. Navigate to OAuth Apps > Create an app"); - println!("3. Set redirect URI to: http://localhost:19876/callback"); + println!("3. Set redirect URI to: http://127.0.0.1:19876/callback"); + println!( + " This must match exactly, and port 19876 must be available when you run 'hubstaff login'." + ); println!("4. Copy the Client ID and Client Secret below."); println!(); @@ -114,34 +126,18 @@ pub fn setup_oauth() -> Result<(), CliError> { return Err(CliError::Config("client ID cannot be empty".into())); } - print!("Client Secret: "); - io::stdout().flush().unwrap(); - let mut client_secret = String::new(); - io::stdin() - .read_line(&mut client_secret) + let client_secret = rpassword::prompt_password("Client Secret: ") .map_err(|e| CliError::Config(format!("failed to read input: {e}")))?; - let client_secret = client_secret.trim(); - if client_secret.is_empty() { + if client_secret.trim().is_empty() { return Err(CliError::Config("client secret cannot be empty".into())); } - // Write to config dir .env file so it works from any directory - let env_path = Config::config_dir().join(".env"); - let dir = Config::config_dir(); - if !dir.exists() { - std::fs::create_dir_all(&dir)?; - } - std::fs::write( - &env_path, - format!("HUBSTAFF_CLIENT_ID={client_id}\nHUBSTAFF_CLIENT_SECRET={client_secret}\n"), - )?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(&env_path, std::fs::Permissions::from_mode(0o600))?; - } + let dir = Config::ensure_dir()?; + let env_path = dir.join(".env"); + let content = + format!("HUBSTAFF_CLIENT_ID={client_id}\nHUBSTAFF_CLIENT_SECRET={client_secret}\n"); + write_atomic(&env_path, content.as_bytes())?; println!(); println!("Saved to {}", env_path.display()); diff --git a/src/login.rs b/src/login.rs deleted file mode 100644 index b4c48d8..0000000 --- a/src/login.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::auth; -use crate::error::CliError; - -pub fn login() -> Result<(), CliError> { - auth::login() -} - -pub fn logout() -> Result<(), CliError> { - auth::logout() -} diff --git a/src/main.rs b/src/main.rs index 72b905b..6e48453 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod command_index; mod config; mod config_commands; mod error; -mod login; +mod persistence; mod schema; use clap::{Parser, Subcommand}; @@ -25,6 +25,10 @@ struct Cli { /// Output full JSON instead of compact format #[arg(long, global = true)] json: bool, + + /// Override the default organization for this invocation + #[arg(long, global = true)] + organization: Option, } #[derive(Subcommand)] @@ -51,6 +55,10 @@ enum Commands { enum ConfigAction { /// Set a config value (keys: organization, api_url, auth_url, schema_url, token, format) Set { key: String, value: String }, + /// Unset a config value and restore its default (keys: organization, api_url, auth_url, schema_url, format) + Unset { key: String }, + /// Reset all non-auth config values to defaults (does not clear tokens; use 'hubstaff logout') + Reset, /// Authenticate with a personal access token (exchanges it automatically) SetPat { /// Your personal access token from developer.hubstaff.com @@ -93,12 +101,14 @@ fn run(cli: &Cli) -> Result<(), error::CliError> { match &cli.command { Commands::Config { action } => match action { ConfigAction::Set { key, value } => config_commands::set(key, value), + ConfigAction::Unset { key } => config_commands::unset(key), + ConfigAction::Reset => config_commands::reset(), ConfigAction::SetPat { token } => config_commands::set_pat(token), ConfigAction::SetupOauth => config_commands::setup_oauth(), ConfigAction::Show => config_commands::show(), }, - Commands::Login => login::login(), - Commands::Logout => login::logout(), + Commands::Login => auth::login(), + Commands::Logout => auth::logout(), Commands::Schema { action } => { let cfg = config::Config::load()?; match action { @@ -118,7 +128,7 @@ fn run(cli: &Cli) -> Result<(), error::CliError> { let cfg = config::Config::load()?; let schema = schema::ApiSchema::load(&cfg)?; let mut client = HubstaffClient::new(cfg)?; - api::run_dynamic(&mut client, &schema, args, cli.json) + api::run_dynamic(&mut client, &schema, args, cli.json, cli.organization) } } } diff --git a/src/persistence.rs b/src/persistence.rs new file mode 100644 index 0000000..d76ee58 --- /dev/null +++ b/src/persistence.rs @@ -0,0 +1,117 @@ +use crate::error::CliError; +use std::io::Write; +use std::path::Path; +use tempfile::NamedTempFile; + +pub fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), CliError> { + let parent = path.parent().unwrap_or_else(|| Path::new(".")); + let mut temp = NamedTempFile::new_in(parent)?; + temp.write_all(bytes)?; + temp.flush()?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(temp.path(), std::fs::Permissions::from_mode(0o600))?; + } + + temp.persist(path).map_err(|e| CliError::from(e.error))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::path::PathBuf; + use std::sync::atomic::{AtomicU32, Ordering}; + + static TEST_SEQ: AtomicU32 = AtomicU32::new(0); + + struct TestDir(PathBuf); + + impl TestDir { + fn new(label: &str) -> Self { + let id = TEST_SEQ.fetch_add(1, Ordering::SeqCst); + let pid = std::process::id(); + let path = + std::env::temp_dir().join(format!("hubstaff-persistence-{pid}-{id}-{label}")); + let _ = fs::remove_dir_all(&path); + fs::create_dir_all(&path).unwrap(); + Self(path) + } + + fn path(&self) -> &Path { + &self.0 + } + } + + impl Drop for TestDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + + #[test] + fn write_atomic_creates_file_with_contents() { + let dir = TestDir::new("create"); + let path = dir.path().join("data.bin"); + write_atomic(&path, b"hello").unwrap(); + assert_eq!(fs::read(&path).unwrap(), b"hello"); + } + + #[test] + fn write_atomic_overwrites_existing_file() { + let dir = TestDir::new("overwrite"); + let path = dir.path().join("data.bin"); + fs::write(&path, b"old").unwrap(); + write_atomic(&path, b"new").unwrap(); + assert_eq!(fs::read(&path).unwrap(), b"new"); + } + + #[test] + fn write_atomic_removes_temp_file() { + let dir = TestDir::new("tempfile"); + let path = dir.path().join("data.bin"); + write_atomic(&path, b"x").unwrap(); + let entries: Vec<_> = fs::read_dir(dir.path()) + .unwrap() + .filter_map(Result::ok) + .map(|e| e.path()) + .collect(); + assert_eq!(entries, vec![path]); + } + + #[test] + fn write_atomic_concurrent_writes_do_not_collide() { + let dir = TestDir::new("concurrent"); + let path = dir.path().join("data.bin"); + let p1 = path.clone(); + let p2 = path.clone(); + let h1 = std::thread::spawn(move || { + for _ in 0..50 { + write_atomic(&p1, b"AAAA").unwrap(); + } + }); + let h2 = std::thread::spawn(move || { + for _ in 0..50 { + write_atomic(&p2, b"BBBB").unwrap(); + } + }); + h1.join().unwrap(); + h2.join().unwrap(); + let final_bytes = fs::read(&path).unwrap(); + assert!(final_bytes == b"AAAA" || final_bytes == b"BBBB"); + } + + #[cfg(unix)] + #[test] + fn write_atomic_sets_0o600_on_unix() { + use std::os::unix::fs::PermissionsExt; + let dir = TestDir::new("perms"); + let path = dir.path().join("data.bin"); + write_atomic(&path, b"x").unwrap(); + let mode = fs::metadata(&path).unwrap().permissions().mode() & 0o777; + assert_eq!(mode, 0o600); + } +} diff --git a/src/schema.rs b/src/schema.rs index 9cbdfdc..4176620 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,5 +1,6 @@ use crate::config::Config; use crate::error::CliError; +use crate::persistence::write_atomic; use chrono::Utc; use reqwest::blocking::Client; use reqwest::header::{ETAG, IF_NONE_MATCH}; @@ -8,7 +9,6 @@ use serde_json::{Map, Value}; use sha2::{Digest, Sha256}; use std::collections::HashMap; use std::fs; -use std::path::Path; use std::time::Duration as StdDuration; const DEFAULT_SCHEMA_TIMEOUT_SECS: u64 = 10; @@ -186,7 +186,7 @@ impl ApiSchema { .and_then(|index| self.operations.get(*index)) } - fn from_schema( + pub(crate) fn from_schema( schema: &Value, source: SchemaSource, cache_meta: Option, @@ -626,20 +626,6 @@ fn write_meta(meta: &SchemaCacheMeta) -> Result<(), CliError> { write_atomic(&Config::schema_meta_path(), content.as_bytes()) } -fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), CliError> { - let temp_path = path.with_extension("tmp"); - fs::write(&temp_path, bytes)?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - fs::set_permissions(&temp_path, fs::Permissions::from_mode(0o600))?; - } - - fs::rename(&temp_path, path)?; - Ok(()) -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/snapshots/hubstaff__command_index__tests__schema_command_table_snapshot.snap b/src/snapshots/hubstaff__command_index__tests__schema_command_table_snapshot.snap new file mode 100644 index 0000000..0b70e48 --- /dev/null +++ b/src/snapshots/hubstaff__command_index__tests__schema_command_table_snapshot.snap @@ -0,0 +1,143 @@ +--- +source: src/command_index.rs +assertion_line: 477 +expression: table +--- +DELETE /insights/smart_notification_rules/{rule_id} -> insights smart_notification_rules +DELETE /invites/{invite_id} -> invites delete +DELETE /organizations/{organization_id}/global_todos/{id} -> global_todos delete +DELETE /tasks/{task_id} -> tasks delete +DELETE /teams/{team_id} -> teams delete +DELETE /time_off_policies/{time_off_policy_id} -> time_off_policies delete +DELETE /time_off_requests/{time_off_request_id} -> time_off_requests delete +DELETE /webhooks/{webhook_id} -> webhooks delete +GET /budget_histories/{budget_history_id} -> budget_histories get +GET /clients/{client_id} -> clients get +GET /clients/{client_id}/budget_histories -> clients budget_histories list +GET /error_codes -> error_codes list +GET /insights/smart_notification_rules/{rule_id} -> insights smart_notification_rules +GET /integrations/{integration_id}/project_links -> integrations project_links list +GET /integrations/{integration_id}/tasks -> integrations tasks list +GET /integrations/{integration_id}/user_links -> integrations user_links list +GET /invites/{invite_id} -> invites get +GET /job_sites/{job_site_id} -> job_sites get +GET /limit_histories/{limit_history_id} -> limit_histories get +GET /notes/{note_id} -> notes get +GET /organizations -> organizations list +GET /organizations/{organization_id} -> organizations get +GET /organizations/{organization_id}/activities -> activities list +GET /organizations/{organization_id}/activities/daily -> activities daily +GET /organizations/{organization_id}/activities/daily/updates -> activities daily updates +GET /organizations/{organization_id}/activities/updates -> activities updates +GET /organizations/{organization_id}/attendance_schedules -> attendance_schedules list +GET /organizations/{organization_id}/attendance_shifts -> attendance_shifts list +GET /organizations/{organization_id}/budget_histories -> budget_histories list +GET /organizations/{organization_id}/client_invoices -> client_invoices list +GET /organizations/{organization_id}/clients -> clients list +GET /organizations/{organization_id}/global_todos -> global_todos list +GET /organizations/{organization_id}/holidays -> holidays list +GET /organizations/{organization_id}/insights/smart_notification_rules -> insights smart_notification_rules +GET /organizations/{organization_id}/insights/smart_notifications -> insights smart_notifications +GET /organizations/{organization_id}/insights/unusual_activities -> insights unusual_activities +GET /organizations/{organization_id}/integrations -> integrations list +GET /organizations/{organization_id}/invites -> invites list +GET /organizations/{organization_id}/job_site_visits -> job_site_visits list +GET /organizations/{organization_id}/job_sites -> job_sites list +GET /organizations/{organization_id}/last_activities -> last_activities list +GET /organizations/{organization_id}/limit_histories -> limit_histories list +GET /organizations/{organization_id}/locations -> locations list +GET /organizations/{organization_id}/members -> members list +GET /organizations/{organization_id}/notes -> notes list +GET /organizations/{organization_id}/passive_job_site_visits -> passive_job_site_visits list +GET /organizations/{organization_id}/projects -> projects list +GET /organizations/{organization_id}/rates -> rates list +GET /organizations/{organization_id}/screenshots -> screenshots list +GET /organizations/{organization_id}/screenshots/{screenshot_id} -> screenshots get +GET /organizations/{organization_id}/seats -> seats list +GET /organizations/{organization_id}/tasks -> tasks list +GET /organizations/{organization_id}/team_invoices -> team_invoices list +GET /organizations/{organization_id}/team_payments -> team_payments list +GET /organizations/{organization_id}/teams -> teams list +GET /organizations/{organization_id}/time_edit_logs -> time_edit_logs list +GET /organizations/{organization_id}/time_off_balances -> time_off_balances list +GET /organizations/{organization_id}/time_off_policies -> time_off_policies list +GET /organizations/{organization_id}/time_off_policies/user_policies -> time_off_policies user_policies +GET /organizations/{organization_id}/time_off_requests -> time_off_requests list +GET /organizations/{organization_id}/timesheets -> timesheets list +GET /organizations/{organization_id}/tool_usages -> tool_usages list +GET /organizations/{organization_id}/tool_usages/daily -> tool_usages daily +GET /organizations/{organization_id}/work_break_projects -> work_break_projects list +GET /projects/{project_id} -> projects get +GET /projects/{project_id}/activities -> projects activities list +GET /projects/{project_id}/activities/daily -> projects activities daily +GET /projects/{project_id}/activities/daily/updates -> projects activities daily updates +GET /projects/{project_id}/activities/updates -> projects activities updates +GET /projects/{project_id}/budget_histories -> projects budget_histories list +GET /projects/{project_id}/integration_links -> projects integration_links list +GET /projects/{project_id}/last_activities -> projects last_activities list +GET /projects/{project_id}/locations -> projects locations list +GET /projects/{project_id}/members -> projects members list +GET /projects/{project_id}/notes -> projects notes list +GET /projects/{project_id}/screenshots -> projects screenshots list +GET /projects/{project_id}/screenshots/{screenshot_id} -> projects screenshots get +GET /projects/{project_id}/tasks -> projects tasks list +GET /projects/{project_id}/time_edit_logs -> projects time_edit_logs list +GET /projects/{project_id}/tool_usages -> projects tool_usages list +GET /projects/{project_id}/tool_usages/daily -> projects tool_usages daily +GET /tasks/{task_id} -> tasks get +GET /team_payments/{team_payment_id} -> team_payments get +GET /teams/{team_id} -> teams get +GET /teams/{team_id}/members -> teams members list +GET /teams/{team_id}/projects -> teams projects list +GET /time_off_policies/{time_off_policy_id} -> time_off_policies get +GET /time_off_requests/{time_off_request_id} -> time_off_requests get +GET /users/me -> users me +GET /users/{user_id} -> users get +GET /webhooks/{webhook_id} -> webhooks get +PATCH /insights/smart_notification_rules/{rule_id}/toggle_status -> insights smart_notification_rules toggle_status +POST /organizations -> organizations create +POST /organizations/{organization_id}/attendance_schedules -> attendance_schedules create +POST /organizations/{organization_id}/clients -> clients create +POST /organizations/{organization_id}/global_todos -> global_todos create +POST /organizations/{organization_id}/insights/smart_notification_rules -> insights smart_notification_rules +POST /organizations/{organization_id}/invites -> invites create +POST /organizations/{organization_id}/invites/bulk -> invites bulk +POST /organizations/{organization_id}/job_sites -> job_sites create +POST /organizations/{organization_id}/members -> members create +POST /organizations/{organization_id}/projects -> projects create +POST /organizations/{organization_id}/teams -> teams create +POST /organizations/{organization_id}/time_off_balances -> time_off_balances create +POST /organizations/{organization_id}/time_off_policies -> time_off_policies create +POST /organizations/{organization_id}/time_off_requests -> time_off_requests create +POST /organizations/{organization_id}/webhooks -> webhooks create +POST /projects/{project_id}/tasks -> projects tasks create +POST /projects/{project_id}/webhooks -> projects webhooks create +POST /time_off_policies/{time_off_policy_id}/archive -> time_off_policies archive create +POST /time_off_policies/{time_off_policy_id}/restore -> time_off_policies restore create +POST /users/me/webhooks -> users me webhooks +POST /users/{user_id}/time_entries -> users time_entries create +POST /webhooks/{webhook_id}/activate -> webhooks activate create +PUT /attendance_schedules/{attendance_schedule_id} -> attendance_schedules update +PUT /clients/{client_id} -> clients update +PUT /clients/{client_id}/update_metadata -> clients update_metadata +PUT /insights/smart_notification_rules/{rule_id} -> insights smart_notification_rules +PUT /invites/{invite_id} -> invites update +PUT /job_sites/{job_site_id} -> job_sites update +PUT /job_sites/{job_site_id}/add_rules -> job_sites add_rules +PUT /job_sites/{job_site_id}/remove_rules -> job_sites remove_rules +PUT /job_sites/{job_site_id}/update_rules -> job_sites update_rules +PUT /organizations/{organization_id}/global_todos/{id} -> global_todos update +PUT /organizations/{organization_id}/update_members -> organizations update_members +PUT /organizations/{organization_id}/update_metadata -> organizations update_metadata +PUT /projects/{project_id} -> projects update +PUT /projects/{project_id}/update_members -> projects update_members +PUT /projects/{project_id}/update_metadata -> projects update_metadata +PUT /tasks/{task_id} -> tasks update +PUT /tasks/{task_id}/update_metadata -> tasks update_metadata +PUT /teams/{team_id} -> teams update +PUT /teams/{team_id}/update_members -> teams update_members +PUT /teams/{team_id}/update_metadata -> teams update_metadata +PUT /teams/{team_id}/update_projects -> teams update_projects +PUT /time_off_policies/{time_off_policy_id} -> time_off_policies update +PUT /time_off_requests/{time_off_request_id} -> time_off_requests update +PUT /time_off_requests/{time_off_request_id}/status -> time_off_requests status diff --git a/tests/commands_test.rs b/tests/commands_test.rs index b68dd8c..846a8b4 100644 --- a/tests/commands_test.rs +++ b/tests/commands_test.rs @@ -7,6 +7,10 @@ mod helpers { PathBuf::from(env!("CARGO_BIN_EXE_hubstaff")) } + pub fn temp_xdg() -> tempfile::TempDir { + tempfile::TempDir::new().expect("failed to create temp XDG dir") + } + pub fn run(args: &[&str], xdg_dir: &str) -> (String, String, i32) { let mut cmd = std::process::Command::new(cli_bin()); cmd.args(args); @@ -81,27 +85,27 @@ mod helpers { #[test] fn cli_version() { - let (stdout, _, code) = helpers::run(&["--version"], "/tmp/hcli-test-ver"); + let xdg = helpers::temp_xdg(); + let (stdout, _, code) = helpers::run(&["--version"], xdg.path().to_str().unwrap()); assert_eq!(code, 0); assert!(stdout.contains("hubstaff")); - let _ = std::fs::remove_dir_all("/tmp/hcli-test-ver"); } #[test] fn cli_help_lists_hardcoded_commands() { - let (stdout, _, code) = helpers::run(&["--help"], "/tmp/hcli-test-help"); + let xdg = helpers::temp_xdg(); + let (stdout, _, code) = helpers::run(&["--help"], xdg.path().to_str().unwrap()); assert_eq!(code, 0); for cmd in ["schema", "config", "login", "logout"] { assert!(stdout.contains(cmd), "missing command: {cmd}"); } assert!(!stdout.contains("api")); - let _ = std::fs::remove_dir_all("/tmp/hcli-test-help"); } #[test] fn cli_config_set_and_show() { - let dir = "/tmp/hcli-test-cfg"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); // Set organization let (stdout, _, code) = helpers::run(&["config", "set", "organization", "42"], dir); @@ -143,38 +147,32 @@ fn cli_config_set_and_show() { let (stdout, _, code) = helpers::run(&["config", "show"], dir); assert_eq!(code, 0); assert!(stdout.contains("auth_url = https://account.staging.hbstf.co")); - - let _ = std::fs::remove_dir_all(dir); } #[test] fn cli_config_set_invalid_key() { - let dir = "/tmp/hcli-test-cfg-inv"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); let (_, stderr, code) = helpers::run(&["config", "set", "bad_key", "val"], dir); assert_eq!(code, 3); assert!(stderr.contains("unknown config key")); - - let _ = std::fs::remove_dir_all(dir); } #[test] fn cli_config_set_invalid_format() { - let dir = "/tmp/hcli-test-cfg-fmt"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); let (_, stderr, code) = helpers::run(&["config", "set", "format", "xml"], dir); assert_eq!(code, 3); assert!(stderr.contains("compact")); - - let _ = std::fs::remove_dir_all(dir); } #[test] fn cli_config_explicit_default_schema_url_is_preserved_with_custom_api_url() { - let dir = "/tmp/hcli-test-schema-url-explicit-default"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); let (stdout, _, code) = helpers::run( &[ @@ -208,14 +206,139 @@ fn cli_config_explicit_default_schema_url_is_preserved_with_custom_api_url() { !stdout.contains("schema_url = https://staging.api.hubstaff.com/v2/docs"), "schema_url should stay on the explicit override" ); +} - let _ = std::fs::remove_dir_all(dir); +#[test] +fn cli_config_set_token_clears_oauth_refresh_state() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + let cfg_dir = xdg.path().join("hubstaff"); + std::fs::create_dir_all(&cfg_dir).unwrap(); + std::fs::write( + cfg_dir.join("config.toml"), + "[auth]\naccess_token = \"old_access\"\nrefresh_token = \"old_refresh\"\nexpires_at = \"2099-01-01T00:00:00Z\"\n", + ) + .unwrap(); + + let (_, _, code) = helpers::run(&["config", "set", "token", "raw_pat"], dir); + assert_eq!(code, 0); + + let content = std::fs::read_to_string(cfg_dir.join("config.toml")).unwrap(); + assert!(content.contains("access_token")); + assert!( + !content.contains("refresh_token"), + "refresh_token should be cleared when setting raw token; got: {content}" + ); + assert!( + !content.contains("expires_at"), + "expires_at should be cleared when setting raw token; got: {content}" + ); +} + +#[test] +fn cli_config_unset_restores_api_url_default() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + let (_, _, code) = helpers::run( + &[ + "config", + "set", + "api_url", + "https://staging.api.hubstaff.com/v2", + ], + dir, + ); + assert_eq!(code, 0); + + let (stdout, _, code) = helpers::run(&["config", "unset", "api_url"], dir); + assert_eq!(code, 0); + assert!(stdout.contains("unset api_url")); + + let (stdout, _, code) = helpers::run(&["config", "show"], dir); + assert_eq!(code, 0); + assert!(stdout.contains("api_url = https://api.hubstaff.com/v2")); +} + +#[test] +fn cli_config_unset_organization_clears_option() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + let (_, _, code) = helpers::run(&["config", "set", "organization", "42"], dir); + assert_eq!(code, 0); + + let (stdout, _, code) = helpers::run(&["config", "unset", "organization"], dir); + assert_eq!(code, 0); + assert!(stdout.contains("unset organization")); + + let (stdout, _, code) = helpers::run(&["config", "show"], dir); + assert_eq!(code, 0); + assert!(!stdout.contains("organization = ")); +} + +#[test] +fn cli_config_unset_token_rejected() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + let (_, stderr, code) = helpers::run(&["config", "unset", "token"], dir); + assert_eq!(code, 3); + assert!(stderr.contains("hubstaff logout")); +} + +#[test] +fn cli_config_unset_unknown_key() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + let (_, stderr, code) = helpers::run(&["config", "unset", "bogus"], dir); + assert_eq!(code, 3); + assert!(stderr.contains("unknown config key")); +} + +#[test] +fn cli_config_reset_restores_defaults_and_preserves_auth() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + + helpers::run(&["config", "set", "organization", "42"], dir); + helpers::run( + &["config", "set", "api_url", "https://custom.example/v2"], + dir, + ); + helpers::run(&["config", "set", "auth_url", "https://auth.example"], dir); + helpers::run( + &[ + "config", + "set", + "schema_url", + "https://custom.example/v2/docs", + ], + dir, + ); + helpers::run(&["config", "set", "format", "json"], dir); + helpers::run(&["config", "set", "token", "secret"], dir); + + let (stdout, _, code) = helpers::run(&["config", "reset"], dir); + assert_eq!(code, 0); + assert!(stdout.contains("Config reset to defaults")); + + let (stdout, _, code) = helpers::run(&["config", "show"], dir); + assert_eq!(code, 0); + assert!(stdout.contains("api_url = https://api.hubstaff.com/v2")); + assert!(!stdout.contains("auth_url = ")); + assert!(!stdout.contains("organization = ")); + assert!(!stdout.contains("schema_url = ")); + assert!(stdout.contains("format = compact")); + assert!(stdout.contains("access_token = ****")); } #[test] fn cli_logout_clears_tokens() { - let dir = "/tmp/hcli-test-logout"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); helpers::run(&["config", "set", "token", "mytoken"], dir); let (stdout, _, code) = helpers::run(&["logout"], dir); @@ -224,14 +347,12 @@ fn cli_logout_clears_tokens() { let (stdout, _, _) = helpers::run(&["config", "show"], dir); assert!(stdout.contains("not configured")); - - let _ = std::fs::remove_dir_all(dir); } #[test] fn dynamic_projects_list_uses_schema_mapping() { - let dir = "/tmp/hcli-test-dynamic-projects"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); let schema_source = "http://127.0.0.1:1/docs"; helpers::seed_schema_cache_with_source_url(dir, schema_source); @@ -258,7 +379,7 @@ fn dynamic_projects_list_uses_schema_mapping() { assert_eq!(code, 0, "stderr={stderr}"); assert!(stdout.contains("\"projects\":[]")); assert!( - std::path::Path::new(dir) + xdg.path() .join("hubstaff") .join("schema") .join("v2") @@ -267,13 +388,41 @@ fn dynamic_projects_list_uses_schema_mapping() { ); mock.assert(); - let _ = std::fs::remove_dir_all(dir); +} + +#[test] +fn dynamic_projects_list_prefers_global_organization_override() { + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); + let schema_source = "http://127.0.0.1:1/docs"; + helpers::seed_schema_cache_with_source_url(dir, schema_source); + + let mut server = mockito::Server::new(); + let mock = server + .mock("GET", "/organizations/9/projects") + .match_header("authorization", "Bearer test_token") + .with_status(200) + .with_header("content-type", "application/json") + .with_body(r#"{"projects":[{"id":1}]}"#) + .create(); + + let api_url = server.url(); + let _ = helpers::run(&["config", "set", "api_url", &api_url], dir); + let _ = helpers::run(&["config", "set", "schema_url", schema_source], dir); + let _ = helpers::run(&["config", "set", "organization", "7"], dir); + let _ = helpers::run(&["config", "set", "token", "test_token"], dir); + + let (stdout, stderr, code) = helpers::run(&["--organization", "9", "projects", "list"], dir); + assert_eq!(code, 0, "stderr={stderr}"); + assert!(stdout.contains(r#""projects":[{"id":1}]"#)); + + mock.assert(); } #[test] fn dynamic_nonstandard_action_uses_literal_segment() { - let dir = "/tmp/hcli-test-dynamic-nonstandard"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); let schema_source = "http://127.0.0.1:1/docs"; helpers::seed_schema_cache_with_source_url(dir, schema_source); @@ -296,13 +445,12 @@ fn dynamic_nonstandard_action_uses_literal_segment() { assert!(stdout.contains("\"ok\":true")); mock.assert(); - let _ = std::fs::remove_dir_all(dir); } #[test] fn dynamic_command_does_not_use_cache_when_source_url_mismatches_effective_schema_url() { - let dir = "/tmp/hcli-test-schema-cache-mismatch"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); helpers::seed_schema_cache(dir); let (stdout, _, code) = helpers::run(&["config", "set", "api_url", "http://127.0.0.1:1"], dir); @@ -315,20 +463,16 @@ fn dynamic_command_does_not_use_cache_when_source_url_mismatches_effective_schem stderr.contains("schema fetch failed"), "expected refresh failure, got stderr={stderr}" ); - - let _ = std::fs::remove_dir_all(dir); } #[test] fn dynamic_command_uses_cache_when_source_url_matches_effective_schema_url() { - use std::path::Path; - - let dir = "/tmp/hcli-test-schema-cache-match"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); helpers::seed_schema_cache(dir); let schema_source = "http://127.0.0.1:1/docs"; - let schema_dir = Path::new(dir).join("hubstaff").join("schema").join("v2"); + let schema_dir = xdg.path().join("hubstaff").join("schema").join("v2"); let meta = format!( "fetched_at = \"2099-01-01T00:00:00Z\"\netag = \"test\"\nsource_url = \"{schema_source}\"\n" ); @@ -353,18 +497,16 @@ fn dynamic_command_uses_cache_when_source_url_matches_effective_schema_url() { assert!(stdout.contains("\"id\":1")); mock.assert(); - let _ = std::fs::remove_dir_all(dir); } #[test] fn schema_show_is_read_only_for_cache_meta() { - use std::path::Path; - - let dir = "/tmp/hcli-test-schema-show-readonly"; - let _ = std::fs::remove_dir_all(dir); + let xdg = helpers::temp_xdg(); + let dir = xdg.path().to_str().unwrap(); helpers::seed_schema_cache(dir); - let meta_path = Path::new(dir) + let meta_path = xdg + .path() .join("hubstaff") .join("schema") .join("v2") @@ -376,6 +518,4 @@ fn schema_show_is_read_only_for_cache_meta() { let after = std::fs::read_to_string(&meta_path).expect("failed to read meta after show"); assert_eq!(before, after, "schema show should not mutate cache meta"); - - let _ = std::fs::remove_dir_all(dir); } diff --git a/tests/fixtures/schema.json b/tests/fixtures/schema.json new file mode 100644 index 0000000..e7f515c --- /dev/null +++ b/tests/fixtures/schema.json @@ -0,0 +1 @@ +{"info":{"title":"Hubstaff V2 API","description":"# Documentation\n\nWelcome to the Hubstaff API v2. This API provides access to time tracking, project management, and team data.\n\n# Webhooks\n\nWebhooks allow you to receive real-time notifications when events occur in your Hubstaff organization.\n\n## Quick Start\n\n1. Create a webhook with your endpoint URL\n2. Respond to the verification request\n3. Activate the webhook\n4. Start receiving events\n\n## Creating a Webhook\n\n```bash\nPOST /v2/organizations/{org_id}/webhooks\n```\n\n```json\n{\n \"events\": [\"*\"],\n \"target_url\": \"https://example.com/webhook\"\n}\n```\n\nThe response includes the webhook `id` needed for activation.\n\n## Endpoint Verification\n\nAfter creation, we send a POST request to your URL with an `X-Hook-Secret` header. Respond with:\n- Status `200`\n- Echo the `X-Hook-Secret` header back\n\n```javascript\n// Node.js\napp.post('/webhook', (req, res) =\u003e {\n const secret = req.headers['x-hook-secret'];\n if (secret) {\n res.setHeader('X-Hook-Secret', secret);\n return res.sendStatus(200);\n }\n // Handle events...\n});\n```\n\n```python\n# Python\n@app.route('/webhook', methods=['POST'])\ndef webhook():\n secret = request.headers.get('X-Hook-Secret')\n if secret:\n response = make_response('OK', 200)\n response.headers['X-Hook-Secret'] = secret\n return response\n # Handle events...\n```\n\n```ruby\n# Ruby\npost '/webhook' do\n secret = request.env['HTTP_X_HOOK_SECRET']\n if secret\n response.headers['X-Hook-Secret'] = secret\n return status 200\n end\n # Handle events...\nend\n```\n\nSave the secret for signature verification.\n\n## Activation\n\n```bash\nPOST /v2/webhooks/{webhook_id}/activate\nX-Hook-Secret: {secret_from_verification}\n```\n\n## Events\n\n| Event | Description |\n|-------|--------------------------------|\n| `*` | All events |\n| `timer.start` | Time tracking started |\n| `timer.stop` | Time tracking stopped |\n| `project.create` | Project created |\n| `client.create` | Client created |\n| `task.create` | Task created |\n| `task.assign` | Task assigned |\n| `task.unassign` | Task unassigned |\n| `schedule.create` | Schedule created |\n| `schedule.update` | Schedule updated |\n| `schedule.delete` | Schedule deleted |\n| `shift.ontime` | Shift started on time |\n| `shift.early` | Shift started early |\n| `shift.late` | Shift started late |\n| `shift.abandoned` | Shift abandoned |\n| `shift.missed` | Shift missed |\n| `work_break.create` | Break started |\n| `activity.create` | Activity created (on request) |\n| `activity.update` | Activity updated (on request) |\n| `activity.delete` | Activity deleted (on request) |\n| `idle_time.start` | Idle time started (beta) |\n| `billing.occupied_seats` | Occupied seats snapshot |\n\n## Occupied Seats Event\n\nFor `billing.occupied_seats`:\n- `occupied_seats` represents the latest available occupied-seat value up to the previous UTC day when the event is sent.\n- The value is snapshot-based (not recalculated live at delivery time).\n- `occupied_seats` is different from purchased/paid seat quota.\n\n## Payload Format\n\n```json\n{\n \"id\": \"69d37f9f-483e-4be1-92c3-b2377203a8cd\",\n \"event\": \"timer.start\",\n \"created_at\": \"2024-06-20T14:10:55.196912\",\n \"payload\": {\n \"organization_id\": 13,\n \"project_id\": 841201,\n \"project_name\": \"DevSecOps\",\n \"task_id\": null,\n \"task_summary\": null,\n \"user_id\": 651956,\n \"user_name\": \"Alex Yarotsky\",\n \"tracking_started_at\": \"2024-06-20T14:10:46.235Z\"\n }\n}\n```\n\n## Signature Verification\n\nAll requests include `X-Hook-Signature` (HMAC-SHA256). Always verify signatures:\n\n```javascript\n// Node.js\nconst crypto = require('crypto');\n\nfunction verify(payload, signature, secret) {\n const expected = crypto.createHmac('sha256', secret).update(payload).digest('hex');\n return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));\n}\n```\n\n```python\n# Python\nimport hmac, hashlib\n\ndef verify(payload, signature, secret):\n expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()\n return hmac.compare_digest(expected, signature)\n```\n\n```ruby\n# Ruby\ndef verify(payload, signature, secret)\n expected = OpenSSL::HMAC.hexdigest('sha256', secret, payload)\n Rack::Utils.secure_compare(expected, signature)\nend\n```\n\n## Deleting a Webhook\n\n```bash\nDELETE /v2/webhooks/{webhook_id}\n```\n","contact":{"email":"api@hubstaff.com"},"version":"2.0"},"swagger":"2.0","produces":["application/json"],"securityDefinitions":{"oauth2":{"type":"oauth2","description":"Hubstaff Account OpenID Connect Authentication","flow":"accessCode","x-nonce":true,"authorizationUrl":"https://account.hubstaff.com/authorizations/new","tokenUrl":"https://account.hubstaff.com/access_tokens","scopes":{"hubstaff:write":"Write only access to the Hubstaff API","hubstaff:read":"Read only access to the Hubstaff API"}}},"security":[{"oauth2":["hubstaff:read","hubstaff:write"]}],"host":"api.hubstaff.com","schemes":["https"],"x-tagGroups":[{"name":"Endpoints","tags":["activities","attendance_schedules","attendance_shifts","budgets","client_invoices","clients","global_todos","holidays","integrations","invites","job_site_visits","job_sites","limits","locations","members","notes","organizations","projects","rates","screenshots","smart_notification_rules","smart_notifications","tasks","team_invoices","team_payments","teams","time_edit_logs","time_entries","time_off_balances","time_off_policies","time_off_requests","timesheets","tool_usages","unusual_activities","users","webhooks"]},{"name":"Reference","tags":["errors"]}],"tags":[{"name":"activities","description":"Get tracked keyboard, mouse, and overall activity levels"},{"name":"attendance_schedules","description":"Define work schedules and expected attendance for team members"},{"name":"attendance_shifts","description":"Monitor shift attendance, punctuality, and missed shifts"},{"name":"budgets","description":"View project budget history and spending data"},{"name":"client_invoices","description":"View invoices generated for clients"},{"name":"clients","description":"Manage client accounts and associate them with projects"},{"name":"global_todos","description":"Manage global to-dos"},{"name":"holidays","description":"View company holidays and non-working days"},{"name":"integrations","description":"View connected third-party tools and services"},{"name":"invites","description":"Send and manage team member invitations"},{"name":"job_site_visits","description":"Track visits to physical job site locations"},{"name":"job_sites","description":"Define physical locations for field work tracking"},{"name":"limits","description":"View daily and weekly hour limits for users"},{"name":"locations","description":"GPS location data captured during time tracking"},{"name":"members","description":"View members and add new members"},{"name":"notes","description":"Add notes and comments to time entries"},{"name":"organizations","description":"Manage organization settings and structure"},{"name":"projects","description":"Create and manage projects for time tracking"},{"name":"rates","description":"View hourly pay and bill rates"},{"name":"screenshots","description":"Access screenshots captured during work sessions"},{"name":"smart_notification_rules","description":"Configure automated notification triggers"},{"name":"smart_notifications","description":"View triggered smart notifications"},{"name":"tasks","description":"Manage tasks within projects for detailed tracking"},{"name":"team_invoices","description":"View invoices for team payments"},{"name":"team_payments","description":"View payment records for team members"},{"name":"teams","description":"Organize members into teams within an organization"},{"name":"time_edit_logs","description":"Audit trail of manual time entry modifications"},{"name":"time_entries","description":"Create manual time entries"},{"name":"time_off_balances","description":"Manage time off balances"},{"name":"time_off_policies","description":"Manage time off policies"},{"name":"time_off_requests","description":"Manage time off requests"},{"name":"timesheets","description":"Weekly timesheet summaries for approval workflows"},{"name":"tool_usages","description":"View application and tool usage data"},{"name":"unusual_activities","description":"Flagged activities that may require review"},{"name":"users","description":"View user accounts and profile information"},{"name":"webhooks","description":"Subscribe to real-time event notifications via HTTP callbacks"},{"name":"errors","description":"API error codes and troubleshooting guide"}],"paths":{"/v2/attendance_schedules/{attendance_schedule_id}":{"put":{"summary":"Update attendance schedule","description":"Updates an attendance schedule.\n\nReturns the updated attendance schedule.\n\nOnly pass in the fields you want to change. Use add_excluded_dates/remove_excluded_dates to modify exclusions incrementally, or excluded_dates to replace them entirely.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"attendance_schedule_id","description":"Attendance schedule ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2AttendanceSchedulesAttendanceScheduleId"}}],"responses":{"200":{"description":"An attendance schedule","schema":{"type":"object","properties":{"attendance_schedule":{"$ref":"#/definitions/AttendanceSchedule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["attendance_schedules"],"operationId":"putV2AttendanceSchedulesAttendanceScheduleId"}},"/v2/budget_histories/{budget_history_id}":{"get":{"summary":"Get budget history","description":"Returns the budget history with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"budget_history_id","description":"Budget History ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A budget history","schema":{"type":"object","properties":{"budget_history":{"$ref":"#/definitions/BudgetHistory"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["budgets"],"operationId":"getV2BudgetHistoriesBudgetHistoryId"}},"/v2/clients/{client_id}":{"get":{"summary":"Get client","description":"Returns the client with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"client_id","description":"Client ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A client","schema":{"type":"object","properties":{"client":{"$ref":"#/definitions/Client"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["clients"],"operationId":"getV2ClientsClientId"},"put":{"summary":"Update client","description":"Updates a client.\n\nReturns the updated client.\n\nOnly pass in the fields you want to change. To unset the budget, specify null as the value. To update the budget, you must specify the complete budget configuration. To clear invoice_notes or net_terms (to use organization defaults), specify null as the value.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"client_id","description":"Client ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2ClientsClientId"}}],"responses":{"200":{"description":"A client","schema":{"type":"object","properties":{"client":{"$ref":"#/definitions/Client"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["clients"],"operationId":"putV2ClientsClientId"}},"/v2/clients/{client_id}/update_metadata":{"put":{"summary":"Update client metadata","description":"Updates metadata on the client.\n\nReturns the updated client.\n\nOnly specify the metadata keys you want to change. If the metadata key does not exist, it will be created. To remove a key, specify null as the value.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"client_id","description":"Client ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2ClientsClientIdUpdateMetadata"}}],"responses":{"200":{"description":"A client","schema":{"type":"object","properties":{"client":{"$ref":"#/definitions/Client"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["clients"],"operationId":"putV2ClientsClientIdUpdateMetadata"}},"/v2/clients/{client_id}/budget_histories":{"get":{"summary":"List client budget histories","description":"Returns budget configuration history (changes to budget settings over time) for the given client.\n\n\n","produces":["application/json"],"parameters":[{"in":"path","name":"client_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of budget histories","schema":{"type":"object","properties":{"budget_histories":{"type":"array","items":{"$ref":"#/definitions/BudgetHistory"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["budgets"],"operationId":"getV2ClientsClientIdBudgetHistories"}},"/v2/error_codes":{"get":{"summary":"List all error codes","description":"Returns all available error codes with their descriptions.\nUse these codes for programmatic error handling and i18n support.\n\nError code ranges:\n- 10000-10999: Authentication \u0026 Authorization\n- 11000-11999: Validation\n- 12000-12999: Resource\n- 13000-13999: Rate Limiting\n- 14000-14999: Time Entries / Activities\n- 15000-15999: System\n","produces":["application/json"],"responses":{"200":{"description":"List of error codes"}},"tags":["errors"],"operationId":"getV2ErrorCodes"}},"/v2/insights/smart_notification_rules/{rule_id}":{"get":{"summary":"Get smart notification rule","description":"Returns the smart notification rule with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"rule_id","description":"smart notification rule ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A smart notification rule","schema":{"type":"object","properties":{"smart_notification_rule":{"$ref":"#/definitions/SmartNotificationRule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"getV2InsightsSmartNotificationRulesRuleId"},"put":{"summary":"Update smart notification rule","description":"Updates a smart notification rule.\n\nReturns the updated smart notification rule.\n\nOnly pass in the fields you want to change.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"rule_id","description":"smart notification rule ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2InsightsSmartNotificationRulesRuleId"}}],"responses":{"200":{"description":"A smart notification rule","schema":{"type":"object","properties":{"smart_notification_rule":{"$ref":"#/definitions/SmartNotificationRule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"putV2InsightsSmartNotificationRulesRuleId"},"delete":{"summary":"Delete smart notification rule","description":"Deletes a smart notification rule.\n","produces":["application/json"],"parameters":[{"in":"path","name":"rule_id","description":"smart notification rule ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Deleted successfully"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"deleteV2InsightsSmartNotificationRulesRuleId"}},"/v2/insights/smart_notification_rules/{rule_id}/toggle_status":{"patch":{"summary":"Toggle smart notification rule","description":"Sets the status of a smart notification rule to either enabled or disabled.\nThe 'enabled' parameter determines the new status of the rule.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"rule_id","description":"smart notification rule ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/patchV2InsightsSmartNotificationRulesRuleIdToggleStatus"}}],"responses":{"200":{"description":"A smart notification rule","schema":{"type":"object","properties":{"smart_notification_rule":{"$ref":"#/definitions/SmartNotificationRule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"patchV2InsightsSmartNotificationRulesRuleIdToggleStatus"}},"/v2/integrations/{integration_id}/project_links":{"get":{"summary":"List integration integration project links","description":"Returns a collection of active project links (mappings between Hubstaff projects and integration projects).\n","produces":["application/json"],"parameters":[{"in":"path","name":"integration_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"a list of project links","schema":{"type":"object","properties":{"project_links":{"type":"array","items":{"$ref":"#/definitions/IntegrationProjectLink"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["integrations"],"operationId":"getV2IntegrationsIntegrationIdProjectLinks"}},"/v2/integrations/{integration_id}/user_links":{"get":{"summary":"List integration integration user links","description":"Returns a collection of active user links (mappings between Hubstaff users and integration users).\n","produces":["application/json"],"parameters":[{"in":"path","name":"integration_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"a list of user links","schema":{"type":"object","properties":{"user_links":{"type":"array","items":{"$ref":"#/definitions/IntegrationUserLink"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["integrations"],"operationId":"getV2IntegrationsIntegrationIdUserLinks"}},"/v2/integrations/{integration_id}/tasks":{"get":{"summary":"List integration tasks","description":"Returns a collection of tasks (to-dos) for the given integration.\n\nResults can be filtered by status[] (active/completed/deleted), user_ids[], project_ids[], and global_todo_ids[].\n","produces":["application/json"],"parameters":[{"in":"path","name":"integration_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"A status or list of statuses to filter","type":"array","items":{"type":"string","enum":["active","completed","deleted","archived","archived_native_active","archived_native_completed","archived_native_deleted"]},"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false},{"in":"query","name":"remote_ids","description":"List of remote_ids to search against remote_id or alternate_remote_id","type":"array","items":{"type":"string"},"required":false}],"responses":{"200":{"description":"A list of tasks","schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/definitions/Task"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"getV2IntegrationsIntegrationIdTasks"}},"/v2/invites/{invite_id}":{"get":{"summary":"Get invite","description":"Returns the invite with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"invite_id","description":"Invite ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"An invite","schema":{"type":"object","properties":{"invite":{"$ref":"#/definitions/Invite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"getV2InvitesInviteId"},"put":{"summary":"Update invite","description":"Updates an invite.\n\nReturns the updated invite.\n\nOnly pass in the fields you want to change. To clear project_ids or pay_rate, send the value 'none'.\n\nAvailable roles:\n- organization_manager - Invited as an organization manager\n- project_manager - Invited as a project manager\n- project_user - Invited as a project user\n- project_viewer - Invited as a project viewer\n\nIf no project_ids are set, any project_* role will default to project_viewer (no projects assigned).\n\nNote: Accepted invites cannot be updated.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"invite_id","description":"Invite ID","type":"integer","format":"int32","required":true},{"name":"putV2InvitesInviteId","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2InvitesInviteId"}}],"responses":{"200":{"description":"An invite","schema":{"type":"object","properties":{"invite":{"$ref":"#/definitions/Invite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"putV2InvitesInviteId"},"delete":{"summary":"Delete invite","description":"Deletes an invite.\n\nNote: Accepted invites cannot be deleted.\n","produces":["application/json"],"parameters":[{"in":"path","name":"invite_id","description":"Invite ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Deletion successful","schema":{"type":"object","properties":{"invite":{"$ref":"#/definitions/Invite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"deleteV2InvitesInviteId"}},"/v2/job_sites/{job_site_id}":{"get":{"summary":"Get job site","description":"Returns the job site with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"job_site_id","description":"Job site ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"getV2JobSitesJobSiteId"},"put":{"summary":"Update job site","description":"Updates a job site.\n\nReturns the updated job site.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"job_site_id","description":"Job site ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2JobSitesJobSiteId"}}],"responses":{"201":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"putV2JobSitesJobSiteId"}},"/v2/job_sites/{job_site_id}/update_rules":{"put":{"summary":"Update job site rules","description":"Updates rules on a job site.\n\nReturns the updated job site.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"job_site_id","description":"Job site ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2JobSitesJobSiteIdUpdateRules"}}],"responses":{"201":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"putV2JobSitesJobSiteIdUpdateRules"}},"/v2/job_sites/{job_site_id}/add_rules":{"put":{"summary":"Add job site rules","description":"Adds new rules to a job site.\n\nReturns the updated job site.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"job_site_id","description":"Job site ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2JobSitesJobSiteIdAddRules"}}],"responses":{"201":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"putV2JobSitesJobSiteIdAddRules"}},"/v2/job_sites/{job_site_id}/remove_rules":{"put":{"summary":"Remove job site rules","description":"Removes rules from a job site.\n\nReturns the updated job site.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"job_site_id","description":"Job site ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2JobSitesJobSiteIdRemoveRules"}}],"responses":{"201":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"putV2JobSitesJobSiteIdRemoveRules"}},"/v2/limit_histories/{limit_history_id}":{"get":{"summary":"Get limit history","description":"Returns the limit history with the given ID.","produces":["application/json"],"parameters":[{"in":"path","name":"limit_history_id","description":"Limit History ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A limit history","schema":{"type":"object","properties":{"limit_history":{"$ref":"#/definitions/LimitHistory"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["limits"],"operationId":"getV2LimitHistoriesLimitHistoryId"}},"/v2/notes/{note_id}":{"get":{"summary":"Get note","description":"Returns the note with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"note_id","description":"Note ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A note","schema":{"type":"object","properties":{"note":{"$ref":"#/definitions/Note"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["notes"],"operationId":"getV2NotesNoteId"}},"/v2/organizations":{"post":{"summary":"Create organization","description":"Creates a new organization.\n\nReturns the created organization.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"postV2Organizations","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2Organizations"}}],"responses":{"200":{"description":"An organization","schema":{"type":"object","properties":{"organization":{"$ref":"#/definitions/Organization"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"postV2Organizations"},"get":{"summary":"List organizations","description":"Returns a collection of organizations the authenticated user is an active member of.\n","produces":["application/json"],"parameters":[{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of organizations","schema":{"type":"object","properties":{"organizations":{"type":"array","items":{"$ref":"#/definitions/Organization"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"getV2Organizations"}},"/v2/organizations/{organization_id}":{"get":{"summary":"Get organization","description":"Returns the organization with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","description":"Organization ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"An organization","schema":{"type":"object","properties":{"organization":{"$ref":"#/definitions/Organization"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"getV2OrganizationsOrganizationId"}},"/v2/organizations/{organization_id}/update_members":{"put":{"summary":"Update organization members","description":"Updates organization members.\n\nReturns {success: true} on success.\n\nOnly pass in the members you want to change.\n\nAvailable roles:\n- user - Member role determined by project membership\n- manager - Organization manager\n- owner - Organization owner\n- remove - Removes user from organization (and all projects/teams)\n\nLimits: Specify full limits config. Missing limits will be removed. Empty \"limits\" removes all limits.\n\nProjects: Specify project memberships to change. Available project roles: viewer, user, manager, remove.\n\nMetadata: Only specify keys to change. Non-existent keys are created. Use null to remove a key.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","description":"Organization ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2OrganizationsOrganizationIdUpdateMembers"}}],"responses":{"200":{"description":"Updated successfully"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"putV2OrganizationsOrganizationIdUpdateMembers"}},"/v2/organizations/{organization_id}/update_metadata":{"put":{"summary":"Update organization metadata","description":"Updates metadata on the organization.\n\nReturns the updated organization.\n\nOnly specify the metadata keys you want to change. If the metadata key does not exist, it will be created. To remove a key, specify null as the value.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","description":"Organization ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2OrganizationsOrganizationIdUpdateMetadata"}}],"responses":{"200":{"description":"An organization","schema":{"type":"object","properties":{"organization":{"$ref":"#/definitions/Organization"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"putV2OrganizationsOrganizationIdUpdateMetadata"}},"/v2/organizations/{organization_id}/activities":{"get":{"summary":"List organization activities","description":"Returns a collection of activities (10-minute time blocks) for the given organization.\n\nResults can be filtered by user, task, project, and time range (time_slot[start], time_slot[stop]).\n\nFor aggregated daily data, use the daily_activities endpoint instead.\n\nNote: Data may be delayed up to 20 minutes.\nNote: Date range limit is 7 days. Earliest date is 6 months ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of global todo IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false},{"in":"query","name":"time_zone","description":"The time zone name for the activity","type":"string","required":false}],"responses":{"200":{"description":"A list of activities","schema":{"type":"object","properties":{"activities":{"type":"array","items":{"$ref":"#/definitions/Activity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2OrganizationsOrganizationIdActivities"}},"/v2/organizations/{organization_id}/activities/updates":{"get":{"summary":"List organization activities updates","description":"Returns activities that were updated within the given time range. Useful for syncing changes.\n\nResults can be filtered by updated[start]/[stop], user_ids[], task_ids[], global_todo_ids[], and project_ids[].\n\nNote: Data may be delayed up to 20 minutes. Date range limit is 7 days. Earliest date is 45 days ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"updated[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"updated[stop]","description":"Stop time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of global todo IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of activities","schema":{"type":"object","properties":{"activities":{"type":"array","items":{"$ref":"#/definitions/Activity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2OrganizationsOrganizationIdActivitiesUpdates"}},"/v2/organizations/{organization_id}/attendance_schedules":{"get":{"summary":"List organization attendance schedules","description":"Returns a collection of attendance schedules (expected work shifts) for the given organization.\n\nUse date[start] and date[stop] to filter schedules that cover the specified date range. Repeating schedules are included if any occurrence falls within the range.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of attendance schedules","schema":{"type":"object","properties":{"attendance_schedules":{"type":"array","items":{"$ref":"#/definitions/AttendanceSchedule"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["attendance_schedules"],"operationId":"getV2OrganizationsOrganizationIdAttendanceSchedules"},"post":{"summary":"Create attendance schedule","description":"Creates an expected work shift for a user.\n\nReturns the created attendance schedule.\n\nFor repeating schedules, specify repeat_schedule (weekly/bi_weekly) and weekdays. Use repeat_until to set an end date. Use excluded_dates to skip specific dates.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdAttendanceSchedules","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdAttendanceSchedules"}}],"responses":{"200":{"description":"attendance schedule created","schema":{"type":"object","properties":{"attendance_schedule":{"$ref":"#/definitions/AttendanceSchedule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["attendance_schedules"],"operationId":"postV2OrganizationsOrganizationIdAttendanceSchedules"}},"/v2/organizations/{organization_id}/attendance_shifts":{"get":{"summary":"List organization attendance shifts","description":"Returns actual work shifts (clock-in/clock-out records) for the given organization.\n\nUse date[start] and date[stop] to filter shifts that fall within the specified date range.\n\nNote: The maximum allowed difference between date[start] and date[stop] is 31 days.\nData is only available within organization’s retention period.\nIf date[start] falls outside this window, the request will fail.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","attendance_schedules"]},"required":false}],"responses":{"200":{"description":"A list of attendance shifts","schema":{"type":"object","properties":{"attendance_shifts":{"type":"array","items":{"$ref":"#/definitions/AttendanceShift"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["attendance_shifts"],"operationId":"getV2OrganizationsOrganizationIdAttendanceShifts"}},"/v2/organizations/{organization_id}/budget_histories":{"get":{"summary":"List organization budget histories","description":"Returns budget configuration history (changes to budget settings over time) for the given organization.\n\nThe project_ids and client_ids filters are processed as a complete set.\n\n- If project_ids 1,2,3 and client_ids 4,5,6 are provided then all budget histories for those\nproject and clients will be returned.\n- If only project_ids 1,2,3 (no client_ids filter) are provided then only budget histories for\nthose projects will be returned (no client budgets at all).\n- If only client_ids 1,2,3 (no project_ids filter) are provided then only budget histories for\nthose clients will be returned (no project budgets at all).\n\nFurther resource may be specified to restrict only returning budget histories for either projects\nor clients.\nThe resource filter is combined with the project_ids/client_ids filters thus\n- If only project_ids is provided and resource = 'client' then no records will be returned\n\nThe start_date filters only apply to the starting date of the start_date of the budget history records.\nThus to fetch only budget histories for this month for all projects specify this filter\n- resource=project and start_date[start]=2026-04-01\n\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"client_ids","description":"List of client IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"resource","description":"Only return budgets for this resource type","type":"string","enum":["project","client"],"required":false},{"in":"query","name":"start_date[start]","description":"Include budget histories with a start date on or after this date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"start_date[stop]","description":"Include budget histories with a start date on or before this date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["clients","projects"]},"required":false}],"responses":{"200":{"description":"A list of budget histories","schema":{"type":"object","properties":{"budget_histories":{"type":"array","items":{"$ref":"#/definitions/BudgetHistory"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["budgets"],"operationId":"getV2OrganizationsOrganizationIdBudgetHistories"}},"/v2/organizations/{organization_id}/client_invoices":{"get":{"summary":"List organization client invoices","description":"Returns invoices sent to clients for the given organization.\n\nResults can be filtered by client_ids[], issue_date[start]/[stop], and status. Use include_line_items=true to include invoice line items.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"client_ids","description":"List of client IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"issue_date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"issue_date[stop]","description":"Stop date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"status","description":"Invoice status","type":"string","enum":["draft","open","closed"],"required":false},{"in":"query","name":"include_line_items","description":"Include invoice line items","type":"boolean","default":false,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","clients","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of client invoices","schema":{"type":"object","properties":{"client_invoices":{"type":"array","items":{"$ref":"#/definitions/ClientInvoice"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["client_invoices"],"operationId":"getV2OrganizationsOrganizationIdClientInvoices"}},"/v2/organizations/{organization_id}/clients":{"get":{"summary":"List organization clients","description":"Returns a collection of clients for the given organization.\n\nResults can be filtered by status.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"Client status","type":"string","default":"active","enum":["active","archived","all"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["projects"]},"required":false}],"responses":{"200":{"description":"A list of clients","schema":{"type":"object","properties":{"clients":{"type":"array","items":{"$ref":"#/definitions/Client"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["clients"],"operationId":"getV2OrganizationsOrganizationIdClients"},"post":{"summary":"Create client","description":"Creates a new client.\n\nReturns the created client.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdClients"}}],"responses":{"200":{"description":"A client","schema":{"type":"object","properties":{"client":{"$ref":"#/definitions/Client"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["clients"],"operationId":"postV2OrganizationsOrganizationIdClients"}},"/v2/organizations/{organization_id}/activities/daily":{"get":{"summary":"List organization daily activities","description":"Returns daily aggregated time tracking data for the given organization.\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], and date[start]/[stop]. Data is aggregated by organization timezone date.\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily activities","schema":{"type":"object","properties":{"daily_activities":{"type":"array","items":{"$ref":"#/definitions/DailyActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2OrganizationsOrganizationIdActivitiesDaily"}},"/v2/organizations/{organization_id}/activities/daily/updates":{"get":{"summary":"List organization daily activities updates","description":"Returns daily activities that were modified within the specified time range. Useful for syncing changes.\n\nUse updated[start] and updated[stop] to specify the modification time range. Results can also be filtered by user_ids[], task_ids[], and project_ids[].\n\nNote: Updated time range limit is 31 days. Only returns activities from the last month.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"updated[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"updated[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily activities","schema":{"type":"object","properties":{"daily_activities":{"type":"array","items":{"$ref":"#/definitions/DailyActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2OrganizationsOrganizationIdActivitiesDailyUpdates"}},"/v2/organizations/{organization_id}/tool_usages/daily":{"get":{"summary":"List organization daily tool usages","description":"Returns daily aggregated time spent using productivity tools (apps and websites categorized by Hubstaff).\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], tool_type, and date[start]/[stop].\n\nRequires Owner or Organization Manager role (with view others data permission).\n\nNote: Date range limit is 14 days. Earliest date is 1 month ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"tool_type","description":"Filter by tool type (application or url)","type":"string","enum":["application","url"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily tool usages","schema":{"type":"object","properties":{"daily_tool_usages":{"type":"array","items":{"$ref":"#/definitions/DailyToolUsage"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tool_usages"],"operationId":"getV2OrganizationsOrganizationIdToolUsagesDaily"}},"/v2/organizations/{organization_id}/global_todos":{"get":{"summary":"List organization global todos","description":"Returns a collection of global to-dos for the given organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["project_ids"]},"required":false}],"responses":{"200":{"description":"A list of to-dos","schema":{"type":"object","properties":{"global_todos":{"type":"array","items":{"$ref":"#/definitions/GlobalTodo"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["global_todos"],"operationId":"getV2OrganizationsOrganizationIdGlobalTodos"},"post":{"summary":"Create global to-do","description":"Creates a global to-do.\n\nReturns the created global to-do.\n\nGlobal to-dos can be added to multiple projects. All members of those projects can track time to them, and they cannot be marked as complete.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdGlobalTodos"}}],"responses":{"201":{"description":"A global to-do","schema":{"type":"object","properties":{"global_todo":{"$ref":"#/definitions/GlobalTodo"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["global_todos"],"operationId":"postV2OrganizationsOrganizationIdGlobalTodos"}},"/v2/organizations/{organization_id}/global_todos/{id}":{"put":{"summary":"Update global to-do","description":"Updates a global to-do.\n\nReturns the updated global to-do.\n\nGlobal to-dos can be added to multiple projects. All members of those projects can track time to them, and they cannot be marked as complete.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"path","name":"id","description":"Global to-do ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2OrganizationsOrganizationIdGlobalTodosId"}}],"responses":{"200":{"description":"A global to-do","schema":{"type":"object","properties":{"global_todo":{"$ref":"#/definitions/GlobalTodo"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["global_todos"],"operationId":"putV2OrganizationsOrganizationIdGlobalTodosId"},"delete":{"summary":"Delete global to-do","description":"Deletes a global to-do.\n\nNote: This action is irreversible. Deleted global to-dos cannot be combined and reported with any new global to-dos, even if they have the same name.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"path","name":"id","description":"Global to-do ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Global to-do deleted","schema":{"type":"object","properties":{"global_todo":{"$ref":"#/definitions/GlobalTodo"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["global_todos"],"operationId":"deleteV2OrganizationsOrganizationIdGlobalTodosId"}},"/v2/organizations/{organization_id}/holidays":{"get":{"summary":"List organization holidays","description":"Returns a collection of holidays for the given organization.\n\nResults can be filtered by created[start]/[stop] and date[start]/[stop].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":false}],"responses":{"200":{"description":"A list of holidays","schema":{"type":"object","properties":{"holidays":{"type":"array","items":{"$ref":"#/definitions/Holiday"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["holidays"],"operationId":"getV2OrganizationsOrganizationIdHolidays"}},"/v2/organizations/{organization_id}/insights/smart_notification_rules":{"get":{"summary":"List smart notification rules","description":"Returns a collection of smart notification rules for the given organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of smart notification rules","schema":{"type":"object","properties":{"smart_notification_rules":{"type":"array","items":{"$ref":"#/definitions/SmartNotificationRule"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"getV2OrganizationsOrganizationIdInsightsSmartNotificationRules"},"post":{"summary":"Create smart notification rule","description":"Creates a new smart notification rule.\n\nReturns the created smart notification rule. New rules are enabled by default.\n\nNote: The `metric_type` of type `time_spent_in_tool` (used to measure time spent in an app/url) is not yet supported.\n\nTarget field options:\n- To target all members: `{\"all_members\": true}`\n- To target specific teams: `{\"team_ids\": [team_id]}`\n- To target specific members: `{\"user_ids\": [user_id_1, user_id_2]}`\n- You can combine `team_ids` and `user_ids`\n\nRecipients field options:\n- To notify by role: `{\"roles\": [\"owner\", \"manager\"]}`\n- To notify specific members: `{\"user_ids\": [user_id_1, user_id_2]}`\n- You can combine `roles` and `user_ids`\n- Only owners, managers, and team leads can be recipients\n\nDeliver_by field:\n- The value 'insights' is always included (notifications appear on the insights page)\n- You can also include 'email' and 'slack'\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdInsightsSmartNotificationRules"}}],"responses":{"201":{"description":"A smart notification rule","schema":{"type":"object","properties":{"smart_notification_rule":{"$ref":"#/definitions/SmartNotificationRule"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notification_rules"],"operationId":"postV2OrganizationsOrganizationIdInsightsSmartNotificationRules"}},"/v2/organizations/{organization_id}/insights/smart_notifications":{"get":{"summary":"List smart notifications","description":"Returns the smart notification ocurrences for the given organization. By default, the API returns only the occurrences for which the API caller (current user) is a recipient of the notification.\n\nStart and stop dates are required, the remaining parameters are optional.\n\nWhen using the `user_ids` or `team_ids` filter, the API will filter the results based on the users who triggered the notification, not the users who will receive the notification.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"team_ids","description":"List of team IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"rule_id","description":"ID of the smart notification rule to filter by","type":"integer","format":"int32","required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of smart notifications ocurrences","schema":{"type":"object","properties":{"smart_notifications":{"type":"array","items":{"$ref":"#/definitions/SmartNotification"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["smart_notifications"],"operationId":"getV2OrganizationsOrganizationIdInsightsSmartNotifications"}},"/v2/organizations/{organization_id}/insights/unusual_activities":{"get":{"summary":"List unusual activities","description":"Returns a collection of unusual activities (anomalous behavior patterns) for the given organization.\n\nResults can be filtered by user_ids[], time_slot[start]/[stop], confidence_level, and unusual_activity_type.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false},{"in":"query","name":"include_secondary_activities","description":"DEPRECATED: Does nothing, all unusual activity is shown by default","type":"boolean","default":false,"required":false},{"in":"query","name":"confidence_level","description":"Filter by confidence level","type":"string","enum":["high","medium","low"],"required":false},{"in":"query","name":"unusual_activity_type","description":"Filter by unusual activity type","type":"string","enum":["consistent_persistent_activity","consistent_variable_activity","unusual_high_mouse_activity","unusual_high_keyboard_activity","long_period_of_single_mouse_activity","long_period_of_single_keyboard_activity","breakless_work","sustained_high_focus_time","suspicious_activity"],"required":false}],"responses":{"200":{"description":"A list of unusual activity","schema":{"type":"object","properties":{"unusual_activities":{"type":"array","items":{"$ref":"#/definitions/UnusualActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["unusual_activities"],"operationId":"getV2OrganizationsOrganizationIdInsightsUnusualActivities"}},"/v2/organizations/{organization_id}/integrations":{"get":{"summary":"List organization integrations","description":"Returns a collection of integrations for the given organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of integrations","schema":{"type":"object","properties":{"integrations":{"type":"array","items":{"$ref":"#/definitions/Integration"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["integrations"],"operationId":"getV2OrganizationsOrganizationIdIntegrations"}},"/v2/organizations/{organization_id}/invites":{"get":{"summary":"List organization invites","description":"Returns a collection of invites for the given organization.\n\nResults can be filtered by email and status.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"email","description":"An email to search for a pending invitation by","type":"string","required":false},{"in":"query","name":"status","description":"Filter invites by status","type":"string","default":"pending","enum":["all","pending","accepted","expired"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of invites","schema":{"type":"object","properties":{"invites":{"type":"array","items":{"$ref":"#/definitions/Invite"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"getV2OrganizationsOrganizationIdInvites"},"post":{"summary":"Create invite","description":"Creates a new invite.\n\nReturns the created invite.\n\nNote: If there is an account for the email in Hubstaff, the invite will be auto accepted.\nNote: There is a rate limit on how many invites you may create.\n\nAvailable roles:\n- organization_manager - Invited as an organization manager\n- project_manager - Invited as a project manager\n- project_user - Invited as a project user\n- project_viewer - Invited as a project viewer\n\nIf no project_ids are set, any project_* role will default to project_viewer (no projects assigned).\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdInvites","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdInvites"}}],"responses":{"201":{"description":"An invite","schema":{"type":"object","properties":{"invite":{"$ref":"#/definitions/Invite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"postV2OrganizationsOrganizationIdInvites"}},"/v2/organizations/{organization_id}/invites/bulk":{"post":{"summary":"Bulk invite users","description":"Creates multiple invites at once (max 50 per request).\n\nReturns a summary with counts of successful, failed, and rate-limited invites, along with detailed results.\n\nInvites are processed sequentially. Individual failures don't stop remaining invites. If rate limit is exceeded, processing stops and remaining invites are marked as rate-limited.\n\nAvailable roles:\n- organization_manager - Organization manager with manager access to specified projects\n- project_manager - Organization user with manager access to specified projects\n- project_user - Organization user with user access to specified projects\n- project_viewer - Organization user with viewer access to specified projects\n\nCommon failure reasons:\n- validation_error - Invalid email format or other validation issues\n- create_action_error - Business rule violations (already member, plan limits, blacklisted user)\n- rate_limited - Rate limit exceeded during processing\n- system_error - Unexpected system errors\n\nNote: Invalid project IDs are automatically filtered out. Emails are automatically lowercased.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdInvitesBulk","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdInvitesBulk"}}],"responses":{"200":{"description":"Bulk invite results"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["invites"],"operationId":"postV2OrganizationsOrganizationIdInvitesBulk"}},"/v2/organizations/{organization_id}/job_sites":{"get":{"summary":"List organization job sites","description":"Returns a collection of job sites (geofenced locations) for the organization.\n\nResults can be filtered by created[start]/[stop].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false}],"responses":{"200":{"description":"A list of job sites","schema":{"type":"object","properties":{"job_sites":{"type":"array","items":{"$ref":"#/definitions/JobSite"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"getV2OrganizationsOrganizationIdJobSites"},"post":{"summary":"Create job site","description":"Creates a new job site.\n\nReturns the created job site.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdJobSites"}}],"responses":{"201":{"description":"A job site","schema":{"type":"object","properties":{"job_site":{"$ref":"#/definitions/JobSite"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_sites"],"operationId":"postV2OrganizationsOrganizationIdJobSites"}},"/v2/organizations/{organization_id}/job_site_visits":{"get":{"summary":"List organization job site visits","description":"Returns a collection of job site visits (geofence entry/exit records) for the given organization.\n\nResults can be filtered by user_ids[] and enter[start]/[stop].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"enter[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"enter[stop]","description":"Stop time (ISO 8601, exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","job_sites"]},"required":false}],"responses":{"200":{"description":"A list of job site visits","schema":{"type":"object","properties":{"job_site_visits":{"type":"array","items":{"$ref":"#/definitions/JobSiteVisit"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_site_visits"],"operationId":"getV2OrganizationsOrganizationIdJobSiteVisits"}},"/v2/organizations/{organization_id}/last_activities":{"get":{"summary":"List organization last activities","description":"Returns the most recent activity for each member in the organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include_removed","description":"Include users that were removed","type":"boolean","default":false,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"Last activities for members","schema":{"type":"object","properties":{"last_activities":{"type":"array","items":{"$ref":"#/definitions/LastActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2OrganizationsOrganizationIdLastActivities"}},"/v2/organizations/{organization_id}/limit_histories":{"get":{"summary":"List organization limit histories","description":"Returns a collection of limit histories (tracking limit configuration changes) for the given organization.\n\nResults can be filtered by user_ids[], date[start], and limit_type.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"limit_type","description":"The type of limit history","type":"string","enum":["daily","weekly","expected_per_week"],"required":false}],"responses":{"200":{"description":"A list of limit histories","schema":{"type":"object","properties":{"limit_histories":{"type":"array","items":{"$ref":"#/definitions/LimitHistory"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["limits"],"operationId":"getV2OrganizationsOrganizationIdLimitHistories"}},"/v2/organizations/{organization_id}/locations":{"get":{"summary":"List organization locations","description":"Returns a collection of locations (GPS coordinates) for the given organization.\n\nResults can be filtered by time_slot[start]/[stop].\n\nNote: Date range limit is 7 days.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of locations","schema":{"type":"object","properties":{"locations":{"type":"array","items":{"$ref":"#/definitions/Location"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["locations"],"operationId":"getV2OrganizationsOrganizationIdLocations"}},"/v2/organizations/{organization_id}/members":{"get":{"summary":"List organization members","description":"Returns a collection of members for the given organization.\n\nResults can be filtered by search, user IDs, membership role, and date range.\n\n*trackable* - Whether the user can track time. This is one of 3 values\n- *false* -- meaning tracking has been explicitly disabled for this user regardless of their role\n- *true* -- meaning they are allowed to track time\n- *viewer* -- meaning they are allowed to track time, however as they are only a project viewer (or no\n memberships) they implicitly can not track on any project\n\n*membership_role* - The basic role in this organization\n- *owner* - An owner of the organization. This member can do anything within the organization.\n- *manager* - An manager of the organization. This member can do anything within the organization\n except things to do with money. (payroll, invoicing, account billing)\n- *user* - (By project). A member of the organization where permissions are defined by their project memberships\n\n*effective_role* - represents an effective role that combines a member's organization role and their project roles\n- *organization_owner* - The member is an organization owner\n- *organization_manager* - The member is an organization manager\n- *project_manager* - The member is an organization user and a manager on all projects and teams\n- *project_user* - The member is an organization user and a user on all projects and teams\n- *project_viewer* - The member is an organization user and a viewer on all projects and teams\n- *by_project* - The member is an organization user and has mixed roles on their projects and team memberships (advanced in the web UI)\n- *unassigned* - The member has no assigned role and no project memberships (No role in the web UI)\n\n*profile* - The profile information for the user. Only organization owners and managers may see this information.\n- *custom_fields* - The custom fields, defined for organization, in format of \"key\": \"value\". The meaning\n of keys is defined in organization.member_profile_fields.\n\n**Filtering by dates:**\n\nYou can filter members by when they were added to the organization using the `created_at` parameter:\n- Use `created_at[start]` to get members added on or after a specific date\n- Use `created_at[stop]` to get members added before a specific date\n- Use both to get members added within a date range\n\nYou can also filter by when membership was last updated using the `updated_at` parameter:\n- Use `updated_at[start]` to get members updated on or after a specific date\n- Use `updated_at[stop]` to get members updated before a specific date\n- Use both to get members updated within a date range\n\n\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"search[email]","description":"An email to search for members","type":"string","required":false},{"in":"query","name":"search[name]","description":"A name to search for members","type":"string","required":false},{"in":"query","name":"include_removed","description":"Include members that were removed from the organization","type":"boolean","default":false,"required":false},{"in":"query","name":"include_profile","description":"Include member profile information","type":"boolean","default":false,"required":false},{"in":"query","name":"include_projects","description":"Include project memberships for this member","type":"boolean","default":false,"required":false},{"in":"query","name":"project_statuses","description":"Filter projects by status when include_projects=true","type":"array","items":{"type":"string","enum":["active","archived","deleted"]},"required":false},{"in":"query","name":"include_limits","description":"Include member limits information","type":"boolean","default":false,"required":false},{"in":"query","name":"membership_roles","description":"Search by membership role","type":"array","items":{"type":"string","enum":["owner","manager","user","unassigned"]},"required":false},{"in":"query","name":"created_at[start]","description":"Start date for created_at filter (inclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"created_at[stop]","description":"End date for created_at filter (exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"updated_at[start]","description":"Start date for updated_at filter (inclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"updated_at[stop]","description":"End date for updated_at filter (exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of members","schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/definitions/OrganizationMember"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"getV2OrganizationsOrganizationIdMembers"},"post":{"summary":"Create organization member","description":"Creates a new user and adds them to the organization as a member.\n\nReturns the created member.\n\nAvailable roles:\n- organization_manager - Organization manager\n- project_manager - Project manager\n- project_user - Project user\n- project_viewer - Project viewer\n\nAvailable pay periods:\n- none - Disables automatic payments\n- weekly - Pay users weekly\n- twice_per_month - Pay users twice a month\n- biweekly - Pay users bi-weekly\n- monthly - Pay users monthly\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdMembers","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdMembers"}}],"responses":{"201":{"description":"A member","schema":{"type":"object","properties":{"organization_member":{"$ref":"#/definitions/OrganizationMember"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"postV2OrganizationsOrganizationIdMembers"}},"/v2/organizations/{organization_id}/notes":{"get":{"summary":"List organization notes","description":"Returns a collection of notes for the given organization.\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], and time_slot[start]/[stop].\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of notes","schema":{"type":"object","properties":{"notes":{"type":"array","items":{"$ref":"#/definitions/Note"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["notes"],"operationId":"getV2OrganizationsOrganizationIdNotes"}},"/v2/organizations/{organization_id}/seats":{"get":{"summary":"Get organization seats","description":"Returns billing seat counters for the specified organization.\n\n- Paid seats are the total number of seats you are currently paying for.\n- Occupied is the total number of seats that are currently filled.\n- Open seats will be filled automatically when new members are added.\n Any open seats will be removed automatically when a new billing cycle starts,\n bringing down the number of paid seats to match the number of seats occupied.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"Organization seats counters","schema":{"type":"object","properties":{"organization_seat":{"$ref":"#/definitions/OrganizationSeat"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"getV2OrganizationsOrganizationIdSeats"}},"/v2/organizations/{organization_id}/passive_job_site_visits":{"get":{"summary":"List organization passive job site visits","description":"Returns a collection of passive job site visits (automatic location detections without active geofence triggers).\n\nResults can be filtered by user_ids[] and enter[start]/[stop].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"enter[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"enter[stop]","description":"Stop time (ISO 8601, exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","job_sites"]},"required":false}],"responses":{"200":{"description":"A list of passive job site visits","schema":{"type":"object","properties":{"passive_job_site_visits":{"type":"array","items":{"$ref":"#/definitions/PassiveJobSiteVisit"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["job_site_visits"],"operationId":"getV2OrganizationsOrganizationIdPassiveJobSiteVisits"}},"/v2/organizations/{organization_id}/projects":{"get":{"summary":"List organization projects","description":"Returns a collection of projects for the given organization.\n\nResults can be filtered by status (active/archived/all) and project_ids[].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"Project status","type":"string","default":"active","enum":["active","archived","all"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["clients"]},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false}],"responses":{"200":{"description":"A list of projects","schema":{"type":"object","properties":{"projects":{"type":"array","items":{"$ref":"#/definitions/Project"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["projects"],"operationId":"getV2OrganizationsOrganizationIdProjects"},"post":{"summary":"Create project","description":"Creates a new project.\n\nReturns the created project.\n\nOnly name is required. Members can optionally be added with roles: viewer, user, or manager.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdProjects"}}],"responses":{"201":{"description":"A project","schema":{"type":"object","properties":{"project":{"$ref":"#/definitions/Project"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["projects"],"operationId":"postV2OrganizationsOrganizationIdProjects"}},"/v2/organizations/{organization_id}/rates":{"get":{"summary":"List organization rates","description":"Returns a collection of rates (pay/bill rates) for the given organization.\n\nResults can be filtered by user_ids[], kind[], rate_type[], status, start_date, and end_date.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"kind","description":"Array of rate kinds (pay, bill)","type":"array","items":{"type":"string","enum":["pay","bill"]},"required":false},{"in":"query","name":"rate_type","description":"Array of rate types (hourly, fixed)","type":"array","items":{"type":"string","enum":["hourly","fixed"]},"required":false},{"in":"query","name":"status","description":"Rate status (current, past)","type":"string","enum":["current","past"],"required":false},{"in":"query","name":"start_date","description":"Start date filter","type":"string","format":"date-time","required":false},{"in":"query","name":"end_date","description":"End date filter","type":"string","format":"date-time","required":false},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false},{"in":"query","name":"fields","description":"List of fields to include in the response","type":"array","items":{"type":"string"},"collectionFormat":"brackets","required":false}],"responses":{"200":{"description":"A list of rates"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["rates"],"operationId":"getV2OrganizationsOrganizationIdRates"}},"/v2/organizations/{organization_id}/screenshots":{"get":{"summary":"List organization screenshots","description":"Returns a collection of screenshots for the given organization.\n\nResults can be filtered by user_ids[], project_ids[], and time_slot[start]/[stop].\n\nNote: Date range limit is 7 days. Earliest date is 6 months ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of screenshots","schema":{"type":"object","properties":{"screenshots":{"type":"array","items":{"$ref":"#/definitions/Screenshot"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["screenshots"],"operationId":"getV2OrganizationsOrganizationIdScreenshots"}},"/v2/organizations/{organization_id}/screenshots/{screenshot_id}":{"get":{"summary":"Get screenshot","description":"Returns the screenshot with the given ID.","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"path","name":"screenshot_id","description":"Screenshot ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"Screenshot found","schema":{"type":"object","properties":{"screenshot":{"$ref":"#/definitions/Screenshot"}}}},"404":{"description":"Screenshot not found","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["screenshots"],"operationId":"getV2OrganizationsOrganizationIdScreenshotsScreenshotId"}},"/v2/organizations/{organization_id}/tasks":{"get":{"summary":"List organization tasks","description":"Returns a collection of tasks (to-dos) for the given organization.\n\nResults can be filtered by status[] (active/completed/deleted), user_ids[], project_ids[], and global_todo_ids[].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"A status or list of statuses to filter","type":"array","items":{"type":"string","enum":["active","completed","deleted","archived","archived_native_active","archived_native_completed","archived_native_deleted"]},"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of Global To-Do IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"only_global_todos","description":"Return only global to-dos","type":"boolean","required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of tasks","schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/definitions/Task"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"getV2OrganizationsOrganizationIdTasks"}},"/v2/organizations/{organization_id}/team_invoices":{"get":{"summary":"List organization team invoices","description":"Returns a collection of team invoices (internal payroll invoices) for the given organization.\n\nResults can be filtered by user_ids[], issue_date[start]/[stop], and status.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"issue_date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"issue_date[stop]","description":"Stop date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"status","description":"Invoice status","type":"string","enum":["draft","open","closed"],"required":false},{"in":"query","name":"include_line_items","description":"Include invoice line items","type":"boolean","default":false,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of team invoices","schema":{"type":"object","properties":{"team_invoices":{"type":"array","items":{"$ref":"#/definitions/TeamInvoice"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["team_invoices"],"operationId":"getV2OrganizationsOrganizationIdTeamInvoices"}},"/v2/organizations/{organization_id}/team_payments":{"get":{"summary":"List organization team payments","description":"Returns a collection of team payments (payroll disbursements) for the given organization.\n\nResults can be filtered by created[start]/[stop], user_ids[], and project_ids[].\n\nNote: If date filters are not provided, defaults to the last 3 years. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects"]},"required":false}],"responses":{"200":{"description":"A list of team payments","schema":{"type":"object","properties":{"team_payments":{"type":"array","items":{"$ref":"#/definitions/TeamPayment"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["team_payments"],"operationId":"getV2OrganizationsOrganizationIdTeamPayments"}},"/v2/organizations/{organization_id}/teams":{"get":{"summary":"List organization teams","description":"Returns a collection of teams for the given organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of teams","schema":{"type":"object","properties":{"teams":{"type":"array","items":{"$ref":"#/definitions/Team"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"getV2OrganizationsOrganizationIdTeams"},"post":{"summary":"Create team","description":"Creates a new team.\n\nReturns the created team.\n\nOnly name is required. Members can optionally be added with roles: viewer, user, or manager.\n\nNote: When a member lead is set to true, the role is forced to manager.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdTeams"}}],"responses":{"201":{"description":"A team","schema":{"type":"object","properties":{"team":{"$ref":"#/definitions/Team"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"postV2OrganizationsOrganizationIdTeams"}},"/v2/organizations/{organization_id}/time_edit_logs":{"get":{"summary":"List organization time edit logs","description":"Returns a collection of time edit logs (manual time additions/edits/deletions) for the given organization.\n\nResults can be filtered by created[start]/[stop], user_ids[], task_ids[], and project_ids[].\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of time edit logs","schema":{"type":"object","properties":{"time_edit_logs":{"type":"array","items":{"$ref":"#/definitions/TimeEditLog"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_edit_logs"],"operationId":"getV2OrganizationsOrganizationIdTimeEditLogs"}},"/v2/organizations/{organization_id}/time_off_balances":{"get":{"summary":"List organization time off balances","description":"Returns time off balances for the given organization.\n\nEach entry represents one user's balance for one time off policy in the requested year.\n\nUse `year` to query balances for a specific year (defaults to current year in the organization's timezone).\n\nUse `include[]` to sideload related users and time_off_policies.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"search","description":"User name search","type":"string","required":false},{"in":"query","name":"user_ids","description":"Filter by user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"time_off_policy_ids","description":"Filter by time off policy IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"year","description":"Balance year (default: current year in organization timezone)","type":"integer","format":"int32","required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","time_off_policies"]},"required":false}],"responses":{"200":{"description":"A list of time off balances","schema":{"type":"object","properties":{"time_off_balances":{"type":"array","items":{"$ref":"#/definitions/TimeOffBalance"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_balances"],"operationId":"getV2OrganizationsOrganizationIdTimeOffBalances"},"post":{"summary":"Add organization time off balance adjustments","description":"Applies manual balance adjustments for the given organization.\n\nEach entry in balance_adjustments specifies a policy, a list of users, an amount (in seconds),\nand whether to add to or replace the current balance.\n\nIf a user is not yet assigned to the specified policy, they are auto-assigned before the adjustment.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdTimeOffBalances","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdTimeOffBalances"}}],"responses":{"201":{"description":"Success","schema":{"type":"object","properties":{"time_off_balance":{"$ref":"#/definitions/TimeOffBalance"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_balances"],"operationId":"postV2OrganizationsOrganizationIdTimeOffBalances"}},"/v2/organizations/{organization_id}/time_off_policies":{"get":{"summary":"List organization time off policies","description":"Returns a collection of time off policies for the given organization.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"Policy status","type":"string","default":"active","enum":["active","archived","all"],"required":false}],"responses":{"200":{"description":"A list of time off policies","schema":{"type":"object","properties":{"time_off_policies":{"type":"array","items":{"$ref":"#/definitions/TimeOffPolicy"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"getV2OrganizationsOrganizationIdTimeOffPolicies"},"post":{"summary":"Create time off policy","description":"Creates a new time off policy for the given organization.\n\n`accrual_policy` fields by `accrual_type`:\n\n`maximum_to_accrue` is required for `hours_worked`, `annual`, and `joined_date`.\n\n- `none` — `starting_balance` (optional)\n- `hours_worked` — `hours_per` (required), `hours_worked` (required), `maximum_to_accrue` (required)\n- `annual` — `hours_per_year` (required), `maximum_to_accrue` (required)\n- `joined_date` — `hours_per_year` (required), `maximum_to_accrue` (required)\n- `monthly` — `accrual_period` (required: `month` or `year`), `accrual_day` (required: `first_day_of_month`, `monthly_anniversary`, or `last_day_of_month`), `prorate_on_start` (optional); plus `hours_per_month` (required when `accrual_period=month`) or `hours_per_year` (required when `accrual_period=year`)\n\nWhen `membership_rules` are provided, they take precedence over `time_off_policy_user_ids`.\nAdditionally, `auto_add_new_users` defaults to `true` when `membership_rules` are provided.\n\nReturns the created time off policy.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdTimeOffPolicies"}}],"responses":{"201":{"description":"A time off policy","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"postV2OrganizationsOrganizationIdTimeOffPolicies"}},"/v2/organizations/{organization_id}/time_off_policies/user_policies":{"get":{"summary":"List organization time off policies per user","description":"Returns active time off policies that the specified user is a member of.\n\nUsed to determine which policies are available when creating a time off request.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"user_id","description":"User ID to list policies for","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of time off policies","schema":{"type":"object","properties":{"time_off_policies":{"type":"array","items":{"$ref":"#/definitions/TimeOffPolicy"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"getV2OrganizationsOrganizationIdTimeOffPoliciesUserPolicies"}},"/v2/organizations/{organization_id}/time_off_requests":{"get":{"summary":"List organization time off requests","description":"Returns a collection of time off requests for the given organization.\n\nResults can be filtered by created[start]/[stop], starts_at[start]/[stop], approved_at[start]/[stop], and user_ids[].\n\nUse include[] to sideload related users and time_off_policies.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"starts_at[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"starts_at[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"approved_at[start]","description":"Approved start time (ISO 8601)","type":"string","format":"date-time","required":false},{"in":"query","name":"approved_at[stop]","description":"Approved stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","time_off_policies"]},"required":false}],"responses":{"200":{"description":"A list of time off requests","schema":{"type":"object","properties":{"time_off_requests":{"type":"array","items":{"$ref":"#/definitions/TimeOffRequest"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"getV2OrganizationsOrganizationIdTimeOffRequests"},"post":{"summary":"Create time off request","description":"Creates a new time off request.\n\nReturns the created time off request with a balance_preview showing the policy balance impact.\n\ntime_off_request_days must include one entry for every date from starts_at to stops_at. Each entry requires a date and amount_used in seconds (typically 28800 for an 8-hour day). Set amount_used to 0 for excluded weekend or holiday days.\n\nSet require_no_shift_overlap to true to reject the request if it overlaps with scheduled attendance shifts.\n\nremove_shifts deletes overlapping attendance shifts only when the policy does not require approval (the request is auto-approved on creation). When the policy requires approval, this field is ignored — use the status endpoint to remove shifts at approval time instead.\n\nNote: starts_at and stops_at are interpreted as wall-clock times in the target user's configured timezone (any timezone offset in the value is ignored). When all_day is true, times are normalized to 09:00–17:00.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdTimeOffRequests","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdTimeOffRequests"}}],"responses":{"201":{"description":"A time off request","schema":{"type":"object","properties":{"time_off_request":{"$ref":"#/definitions/TimeOffRequest"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"postV2OrganizationsOrganizationIdTimeOffRequests"}},"/v2/organizations/{organization_id}/timesheets":{"get":{"summary":"List organization timesheets","description":"Returns a collection of timesheets (aggregate approval records) for the given organization.\n\nResults can be filtered by date[start]/[stop], approved[start], and status.\n\nNote: Timesheets are aggregate records used by the timesheet approval system, not individual time entries.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":false},{"in":"query","name":"approved[start]","description":"Includes timesheets approved since the specified start date, considering UTC timezone (ISO 8601)","type":"string","format":"date","required":false},{"in":"query","name":"status","description":"Invoice status","type":"string","enum":["open","submitted","approved","denied"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of timesheets","schema":{"type":"object","properties":{"timesheets":{"type":"array","items":{"$ref":"#/definitions/Timesheet"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["timesheets"],"operationId":"getV2OrganizationsOrganizationIdTimesheets"}},"/v2/organizations/{organization_id}/tool_usages":{"get":{"summary":"List organization tool usages","description":"Returns a collection of tool usages (productivity tool usage) for the given organization.\n\nResults can be filtered by time_slot[start]/[stop], user_ids[], task_ids[], project_ids[], and tool_type.\n\nThe preferred endpoints to fetch tool usage data are the daily endpoints.\n\nRequires Owner or Organization Manager role (with view others data permission).\n\nNote: Date range limit is 7 days. Earliest date is 1 month ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"tool_type","description":"Filter by tool type (application or url)","type":"string","enum":["application","url"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of tool usages","schema":{"type":"object","properties":{"tool_usages":{"type":"array","items":{"$ref":"#/definitions/ToolUsage"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tool_usages"],"operationId":"getV2OrganizationsOrganizationIdToolUsages"}},"/v2/organizations/{organization_id}/webhooks":{"post":{"summary":"Create organization webhook","description":"Creates a new webhook subscription for the organization. After creation, Hubstaff will send a POST request to your `target_url` with an empty body and an `X-Hook-Secret` header.\n\nYour endpoint must respond with HTTP 200 and echo the same `X-Hook-Secret` header to verify ownership.\n\nStore the secret value - you'll need it to activate the webhook.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"name":"postV2OrganizationsOrganizationIdWebhooks","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2OrganizationsOrganizationIdWebhooks"}}],"responses":{"201":{"description":"Create organization webhook"}},"tags":["webhooks"],"operationId":"postV2OrganizationsOrganizationIdWebhooks"}},"/v2/organizations/{organization_id}/work_break_projects":{"get":{"summary":"List organization work break projects","description":"Returns a collection of work break projects and their policies for the organization.\n\nResults can be filtered by project_ids[].\n","produces":["application/json"],"parameters":[{"in":"path","name":"organization_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"project_ids","description":"List of project IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false}],"responses":{"200":{"description":"A work break","schema":{"type":"object","properties":{"work_break_project":{"$ref":"#/definitions/WorkBreakProject"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["organizations"],"operationId":"getV2OrganizationsOrganizationIdWorkBreakProjects"}},"/v2/projects/{project_id}":{"get":{"summary":"Get project","description":"Returns the project with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","description":"Project ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A project","schema":{"type":"object","properties":{"project":{"$ref":"#/definitions/Project"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["projects"],"operationId":"getV2ProjectsProjectId"},"put":{"summary":"Update project","description":"Updates a project.\n\nReturns the updated project.\n\nOnly pass in the fields you want to change. To unset the budget, specify null. To update the budget, you must specify the complete budget configuration.\n\nWhen restoring an archived project, you can only change the status column.\n\nRate handling for pay_rate and bill_rate:\n- To remove a rate, pass null (e.g., pay_rate: null)\n- Custom user rates take precedence over default project rates\n- To apply default project rate to a user, remove their custom rate first\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"project_id","description":"Project ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2ProjectsProjectId"}}],"responses":{"200":{"description":"A project","schema":{"type":"object","properties":{"project":{"$ref":"#/definitions/Project"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["projects"],"operationId":"putV2ProjectsProjectId"}},"/v2/projects/{project_id}/update_members":{"put":{"summary":"Update project members","description":"Updates project members.\n\nReturns {success: true} on success.\n\nOnly pass in the members you want to change.\n\nAvailable roles:\n- viewer - View-only access\n- user - Standard member access\n- manager - Project manager access\n- remove - Removes user from project\n\nYou can also specify pay_rate and bill_rate for each member. To remove a rate, pass null.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"project_id","description":"Project ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2ProjectsProjectIdUpdateMembers"}}],"responses":{"200":{"description":"Updated successfully"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"putV2ProjectsProjectIdUpdateMembers"}},"/v2/projects/{project_id}/update_metadata":{"put":{"summary":"Update project metadata","description":"Updates metadata on the project.\n\nReturns the updated project.\n\nOnly specify the metadata keys you want to change. If the metadata key does not exist, it will be created. To remove a key, specify null as the value.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"project_id","description":"Project ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2ProjectsProjectIdUpdateMetadata"}}],"responses":{"200":{"description":"A project","schema":{"type":"object","properties":{"project":{"$ref":"#/definitions/Project"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["projects"],"operationId":"putV2ProjectsProjectIdUpdateMetadata"}},"/v2/projects/{project_id}/activities":{"get":{"summary":"List project activities","description":"Returns a collection of activities (10-minute time blocks) for the given project.\n\nResults can be filtered by user, task, project, and time range (time_slot[start], time_slot[stop]).\n\nFor aggregated daily data, use the daily_activities endpoint instead.\n\nNote: Data may be delayed up to 20 minutes.\nNote: Date range limit is 7 days. Earliest date is 6 months ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of global todo IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false},{"in":"query","name":"time_zone","description":"The time zone name for the activity","type":"string","required":false}],"responses":{"200":{"description":"A list of activities","schema":{"type":"object","properties":{"activities":{"type":"array","items":{"$ref":"#/definitions/Activity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2ProjectsProjectIdActivities"}},"/v2/projects/{project_id}/activities/updates":{"get":{"summary":"List project activities updates","description":"Returns activities that were updated within the given time range. Useful for syncing changes.\n\nResults can be filtered by updated[start]/[stop], user_ids[], task_ids[], global_todo_ids[], and project_ids[].\n\nNote: Data may be delayed up to 20 minutes. Date range limit is 7 days. Earliest date is 45 days ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"updated[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"updated[stop]","description":"Stop time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of global todo IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of activities","schema":{"type":"object","properties":{"activities":{"type":"array","items":{"$ref":"#/definitions/Activity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2ProjectsProjectIdActivitiesUpdates"}},"/v2/projects/{project_id}/budget_histories":{"get":{"summary":"List project budget histories","description":"Returns budget configuration history (changes to budget settings over time) for the given project.\n\n\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"A list of budget histories","schema":{"type":"object","properties":{"budget_histories":{"type":"array","items":{"$ref":"#/definitions/BudgetHistory"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["budgets"],"operationId":"getV2ProjectsProjectIdBudgetHistories"}},"/v2/projects/{project_id}/activities/daily":{"get":{"summary":"List project daily activities","description":"Returns daily aggregated time tracking data for the given project.\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], and date[start]/[stop]. Data is aggregated by organization timezone date.\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily activities","schema":{"type":"object","properties":{"daily_activities":{"type":"array","items":{"$ref":"#/definitions/DailyActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2ProjectsProjectIdActivitiesDaily"}},"/v2/projects/{project_id}/activities/daily/updates":{"get":{"summary":"List project daily activities updates","description":"Returns daily activities that were modified within the specified time range. Useful for syncing changes.\n\nUse updated[start] and updated[stop] to specify the modification time range. Results can also be filtered by user_ids[], task_ids[], and project_ids[].\n\nNote: Updated time range limit is 31 days. Only returns activities from the last month.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"updated[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"updated[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily activities","schema":{"type":"object","properties":{"daily_activities":{"type":"array","items":{"$ref":"#/definitions/DailyActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2ProjectsProjectIdActivitiesDailyUpdates"}},"/v2/projects/{project_id}/tool_usages/daily":{"get":{"summary":"List project daily tool usages","description":"Returns daily aggregated time spent using productivity tools (apps and websites categorized by Hubstaff).\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], tool_type, and date[start]/[stop].\n\nRequires Owner or Organization Manager role (with view others data permission).\n\nNote: Date range limit is 14 days. Earliest date is 1 month ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"date[start]","description":"Start date (ISO 8601)","type":"string","format":"date","required":true},{"in":"query","name":"date[stop]","description":"Stop date (ISO 8601, Inclusive)","type":"string","format":"date","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"tool_type","description":"Filter by tool type (application or url)","type":"string","enum":["application","url"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of daily tool usages","schema":{"type":"object","properties":{"daily_tool_usages":{"type":"array","items":{"$ref":"#/definitions/DailyToolUsage"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tool_usages"],"operationId":"getV2ProjectsProjectIdToolUsagesDaily"}},"/v2/projects/{project_id}/integration_links":{"get":{"summary":"List project integration project links","description":"Returns a collection of active project links (mappings between Hubstaff projects and integration projects).\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false}],"responses":{"200":{"description":"a list of project links","schema":{"type":"object","properties":{"project_links":{"type":"array","items":{"$ref":"#/definitions/IntegrationProjectLink"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["integrations"],"operationId":"getV2ProjectsProjectIdIntegrationLinks"}},"/v2/projects/{project_id}/last_activities":{"get":{"summary":"List project last activities","description":"Returns the most recent activity for each member in the project.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include_removed","description":"Include users that were removed","type":"boolean","default":false,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"Last activities for members","schema":{"type":"object","properties":{"last_activities":{"type":"array","items":{"$ref":"#/definitions/LastActivity"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["activities"],"operationId":"getV2ProjectsProjectIdLastActivities"}},"/v2/projects/{project_id}/locations":{"get":{"summary":"List project locations","description":"Returns a collection of locations (GPS coordinates) for the given project.\n\nResults can be filtered by time_slot[start]/[stop].\n\nNote: Date range limit is 7 days.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of locations","schema":{"type":"object","properties":{"locations":{"type":"array","items":{"$ref":"#/definitions/Location"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["locations"],"operationId":"getV2ProjectsProjectIdLocations"}},"/v2/projects/{project_id}/members":{"get":{"summary":"List project members","description":"Returns a collection of members for the given project.\n\nResults can be filtered by search, user IDs, membership role, and date range.\n\n\n*membership_role* - The permission role the member has in this project\n- *manager* - An manager can track time, see data for any member within the project, can change settings, and manage membership within the project\n- *user* - A user can track time against the project and see only their own data within the project\n- *viewer* - A viewer can access reports based on data for any member within the project but can not change anything\n\n**Note**: A member must have membership within a project in order to track on that project, even if they are an organization owner or manager\n\n**Filtering by dates:**\n\nYou can filter members by when they were added to the project using the `created_at` parameter:\n- Use `created_at[start]` to get members added on or after a specific date\n- Use `created_at[stop]` to get members added before a specific date\n- Use both to get members added within a date range\n\nYou can also filter by when membership was last updated using the `updated_at` parameter:\n- Use `updated_at[start]` to get members updated on or after a specific date\n- Use `updated_at[stop]` to get members updated before a specific date\n- Use both to get members updated within a date range\n\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"search[email]","description":"An email to search for members","type":"string","required":false},{"in":"query","name":"search[name]","description":"A name to search for members","type":"string","required":false},{"in":"query","name":"include_removed","description":"Include members that were removed from the project","type":"boolean","default":false,"required":false},{"in":"query","name":"include_profile","description":"Include member profile information","type":"boolean","default":false,"required":false},{"in":"query","name":"direct_only","description":"If true, only include members assigned directly to the project and not as member of a team working on the project","type":"boolean","default":false,"required":false},{"in":"query","name":"membership_roles","description":"Search by membership role","type":"array","items":{"type":"string","enum":["manager","user","viewer"]},"required":false},{"in":"query","name":"created_at[start]","description":"Start date for created_at filter (inclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"created_at[stop]","description":"End date for created_at filter (exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"updated_at[start]","description":"Start date for updated_at filter (inclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"updated_at[stop]","description":"End date for updated_at filter (exclusive)","type":"string","format":"date-time","required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of members","schema":{"type":"object","properties":{"members":{"type":"array","items":{"$ref":"#/definitions/ProjectMember"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"getV2ProjectsProjectIdMembers"}},"/v2/projects/{project_id}/notes":{"get":{"summary":"List project notes","description":"Returns a collection of notes for the given project.\n\nResults can be filtered by user_ids[], task_ids[], project_ids[], and time_slot[start]/[stop].\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of notes","schema":{"type":"object","properties":{"notes":{"type":"array","items":{"$ref":"#/definitions/Note"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["notes"],"operationId":"getV2ProjectsProjectIdNotes"}},"/v2/projects/{project_id}/screenshots":{"get":{"summary":"List project screenshots","description":"Returns a collection of screenshots for the given project.\n\nResults can be filtered by user_ids[], project_ids[], and time_slot[start]/[stop].\n\nNote: Date range limit is 7 days. Earliest date is 6 months ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of screenshots","schema":{"type":"object","properties":{"screenshots":{"type":"array","items":{"$ref":"#/definitions/Screenshot"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["screenshots"],"operationId":"getV2ProjectsProjectIdScreenshots"}},"/v2/projects/{project_id}/screenshots/{screenshot_id}":{"get":{"summary":"Get screenshot","description":"Returns the screenshot with the given ID.","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"path","name":"screenshot_id","description":"Screenshot ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"Screenshot found","schema":{"type":"object","properties":{"screenshot":{"$ref":"#/definitions/Screenshot"}}}},"404":{"description":"Screenshot not found","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["screenshots"],"operationId":"getV2ProjectsProjectIdScreenshotsScreenshotId"}},"/v2/projects/{project_id}/tasks":{"get":{"summary":"List project tasks","description":"Returns a collection of tasks (to-dos) for the given project.\n\nResults can be filtered by status[] (active/completed/deleted), user_ids[], project_ids[], and global_todo_ids[].\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"status","description":"A status or list of statuses to filter","type":"array","items":{"type":"string","enum":["active","completed","deleted","archived","archived_native_active","archived_native_completed","archived_native_deleted"]},"required":false},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"global_todo_ids","description":"List of Global To-Do IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"only_global_todos","description":"Return only global to-dos","type":"boolean","required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of tasks","schema":{"type":"object","properties":{"tasks":{"type":"array","items":{"$ref":"#/definitions/Task"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"getV2ProjectsProjectIdTasks"},"post":{"summary":"Create task","description":"Creates a new task for this project.\n\nReturns the created task.\n\nNote: If this project is integrated with a 3rd party tool, you must create the task in that tool instead.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2ProjectsProjectIdTasks"}}],"responses":{"200":{"description":"To-do created","schema":{"type":"object","properties":{"task":{"$ref":"#/definitions/Task"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"postV2ProjectsProjectIdTasks"}},"/v2/projects/{project_id}/time_edit_logs":{"get":{"summary":"List project time edit logs","description":"Returns a collection of time edit logs (manual time additions/edits/deletions) for the given project.\n\nResults can be filtered by created[start]/[stop], user_ids[], task_ids[], and project_ids[].\n\nNote: Date range limit is 31 days. Earliest date is 3 years ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"created[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"created[stop]","description":"Stop time (ISO 8601, exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","projects","tasks"]},"required":false}],"responses":{"200":{"description":"A list of time edit logs","schema":{"type":"object","properties":{"time_edit_logs":{"type":"array","items":{"$ref":"#/definitions/TimeEditLog"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_edit_logs"],"operationId":"getV2ProjectsProjectIdTimeEditLogs"}},"/v2/projects/{project_id}/tool_usages":{"get":{"summary":"List project tool usages","description":"Returns a collection of tool usages (productivity tool usage) for the given project.\n\nResults can be filtered by time_slot[start]/[stop], user_ids[], task_ids[], project_ids[], and tool_type.\n\nThe preferred endpoints to fetch tool usage data are the daily endpoints.\n\nRequires Owner or Organization Manager role (with view others data permission).\n\nNote: Date range limit is 7 days. Earliest date is 1 month ago.\n","produces":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"time_slot[start]","description":"Start time (ISO 8601)","type":"string","format":"date-time","required":true},{"in":"query","name":"time_slot[stop]","description":"Stop time (ISO 8601, Exclusive)","type":"string","format":"date-time","required":true},{"in":"query","name":"user_ids","description":"List of user IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"task_ids","description":"List of task IDs","type":"array","items":{"type":"integer","format":"int32"},"required":false},{"in":"query","name":"tool_type","description":"Filter by tool type (application or url)","type":"string","enum":["application","url"],"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users","tasks"]},"required":false}],"responses":{"200":{"description":"A list of tool usages","schema":{"type":"object","properties":{"tool_usages":{"type":"array","items":{"$ref":"#/definitions/ToolUsage"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tool_usages"],"operationId":"getV2ProjectsProjectIdToolUsages"}},"/v2/projects/{project_id}/webhooks":{"post":{"summary":"Create project webhook","description":"Creates a new webhook subscription for the project. After creation, Hubstaff will send a POST request to your `target_url` with an empty body and an `X-Hook-Secret` header.\n\nYour endpoint must respond with HTTP 200 and echo the same `X-Hook-Secret` header to verify ownership.\n\nStore the secret value - you'll need it to activate the webhook.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"project_id","type":"integer","format":"int32","required":true},{"name":"postV2ProjectsProjectIdWebhooks","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2ProjectsProjectIdWebhooks"}}],"responses":{"201":{"description":"Create project webhook"}},"tags":["webhooks"],"operationId":"postV2ProjectsProjectIdWebhooks"}},"/v2/tasks/{task_id}":{"get":{"summary":"Get task","description":"Returns the task with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"task_id","description":"Task ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A task","schema":{"type":"object","properties":{"task":{"$ref":"#/definitions/Task"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"getV2TasksTaskId"},"put":{"summary":"Update task","description":"Updates a task.\n\nReturns the updated task.\n\nOnly pass in the fields you want to change. Requires passing lock_version from the task fetch.\n\nNote: Only simple todos can be updated. 3rd party tasks must be updated in their system.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"task_id","description":"Task ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TasksTaskId"}}],"responses":{"200":{"description":"To-do updated","schema":{"type":"object","properties":{"task":{"$ref":"#/definitions/Task"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"putV2TasksTaskId"},"delete":{"summary":"Delete task","description":"Deletes a task.\n\nNote: Only simple todos can be deleted. 3rd party tasks must be deleted in their system.\n","produces":["application/json"],"parameters":[{"in":"path","name":"task_id","description":"Task ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"To-do deleted","schema":{"type":"object","properties":{"task":{"$ref":"#/definitions/Task"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"deleteV2TasksTaskId"}},"/v2/tasks/{task_id}/update_metadata":{"put":{"summary":"Update task metadata","description":"Updates metadata on the task.\n\nReturns the updated task.\n\nOnly specify the metadata keys you want to change. If the metadata key does not exist, it will be created. To remove a key, specify null as the value. Requires passing lock_version from the task fetch.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"task_id","description":"Task ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TasksTaskIdUpdateMetadata"}}],"responses":{"200":{"description":"A client","schema":{"type":"object","properties":{"task":{"$ref":"#/definitions/Task"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["tasks"],"operationId":"putV2TasksTaskIdUpdateMetadata"}},"/v2/team_payments/{team_payment_id}":{"get":{"summary":"Get team payment","description":"Returns the team payment with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"team_payment_id","description":"Team payment ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A team payment","schema":{"type":"object","properties":{"team_payment":{"$ref":"#/definitions/TeamPayment"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["team_payments"],"operationId":"getV2TeamPaymentsTeamPaymentId"}},"/v2/teams/{team_id}":{"get":{"summary":"Get team","description":"Returns the team with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A team","schema":{"type":"object","properties":{"team":{"$ref":"#/definitions/Team"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"getV2TeamsTeamId"},"put":{"summary":"Update team","description":"Updates a team.\n\nReturns the updated team.\n\nOnly pass in the fields you want to change. Only organization owners or managers can update team details.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TeamsTeamId"}}],"responses":{"200":{"description":"A team","schema":{"type":"object","properties":{"team":{"$ref":"#/definitions/Team"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"putV2TeamsTeamId"},"delete":{"summary":"Delete team","description":"Deletes a team.\n\nNote: SCIM-managed teams cannot be deleted.\n","produces":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Team successfully deleted","schema":{"type":"object","properties":{"team":{"$ref":"#/definitions/Team"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"deleteV2TeamsTeamId"}},"/v2/teams/{team_id}/members":{"get":{"summary":"List team members","description":"Returns a collection of members for the given team.\n\nMembership roles:\n- manager - Can track time, view all data, change settings, and manage membership\n- user - Can track time and view only their own data\n- viewer - Can view reports but cannot make changes\n","produces":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["users"]},"required":false}],"responses":{"200":{"description":"A list of members","schema":{"type":"object","properties":{"team_members":{"type":"array","items":{"$ref":"#/definitions/TeamMember"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"getV2TeamsTeamIdMembers"}},"/v2/teams/{team_id}/projects":{"get":{"summary":"List team projects","description":"Returns a collection of projects for the given team.\n","produces":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"in":"query","name":"page_start_id","description":"The page start ID.","type":"integer","format":"int32","default":0,"required":false},{"in":"query","name":"page_limit","description":"The default page size","type":"integer","format":"int32","default":null,"required":false},{"in":"query","name":"include","description":"Specify related data to side load.","type":"array","items":{"type":"string","enum":["projects"]},"required":false}],"responses":{"200":{"description":"A list of projects","schema":{"type":"object","properties":{"team_projects":{"type":"array","items":{"$ref":"#/definitions/TeamProject"}}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"getV2TeamsTeamIdProjects"}},"/v2/teams/{team_id}/update_members":{"put":{"summary":"Update team members","description":"Updates team members.\n\nReturns {success: true} on success.\n\nOnly pass in the members you want to change.\n\nAvailable roles:\n- viewer - View-only access\n- user - Standard member access\n- manager - Team manager access\n- remove - Removes user from team\n\nNote: A team lead must have the manager role. Only organization owners or managers can adjust the lead option.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TeamsTeamIdUpdateMembers"}}],"responses":{"200":{"description":"Updated successfully"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["members"],"operationId":"putV2TeamsTeamIdUpdateMembers"}},"/v2/teams/{team_id}/update_projects":{"put":{"summary":"Update team projects","description":"Updates team projects.\n\nReturns {success: true} on success.\n\nAfter adding or removing projects, member roles in those projects are updated based on team membership.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TeamsTeamIdUpdateProjects"}}],"responses":{"200":{"description":"Updated successfully"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"putV2TeamsTeamIdUpdateProjects"}},"/v2/teams/{team_id}/update_metadata":{"put":{"summary":"Update team metadata","description":"Updates metadata on the team.\n\nReturns the updated team.\n\nOnly specify the metadata keys you want to change. If the metadata key does not exist, it will be created. To remove a key, specify null as the value.\n\nKey requirements:\n- 2-30 characters\n- Lowercase letters, numbers, and underscores only\n\nValue requirements:\n- Valid UTF-8 string\n- Maximum 200 characters\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"team_id","description":"Team ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TeamsTeamIdUpdateMetadata"}}],"responses":{"200":{"description":"A team","schema":{"type":"object","properties":{"team":{"$ref":"#/definitions/Team"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["teams"],"operationId":"putV2TeamsTeamIdUpdateMetadata"}},"/v2/time_off_policies/{time_off_policy_id}":{"get":{"summary":"Get time off policy","description":"Returns the time off policy with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"time_off_policy_id","description":"Time off policy ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A time off policy","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"getV2TimeOffPoliciesTimeOffPolicyId"},"put":{"summary":"Update time off policy","description":"Updates a time off policy. Only pass in the fields you want to change.\n\n**Updatable fields:** `name`, `accrual_policy`, `allow_negative_balances`,\n`requires_approval`, `balance_rolls_over_annually`, `paid`, `auto_add_new_users`,\nand `time_off_policy_user_ids` (list-based policies only).\n\n**Immutable after creation:** `accrual_type` and `membership_rules`.\nUse `accrual_policy` to update the configuration within the existing accrual type.\nFor rules-based policies, member assignment cannot be modified via the API.\n\nReturns the updated time off policy.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"time_off_policy_id","description":"Time off policy ID","type":"integer","format":"int32","required":true},{"name":"data","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TimeOffPoliciesTimeOffPolicyId"}}],"responses":{"200":{"description":"A time off policy","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"putV2TimeOffPoliciesTimeOffPolicyId"},"delete":{"summary":"Delete time off policy","description":"Deletes the time off policy.\n\nA policy cannot be deleted if any of its members have:\n- paid time off requests,\n- approved time off requests, or\n- used time off hours (time off adjustments).\n\nOther balance adjustments (accruals, manual adjustments) do not prevent deletion\nand are removed along with the policy.\n\nUse the archive endpoint instead to retire a policy that has been used.\n","produces":["application/json"],"parameters":[{"in":"path","name":"time_off_policy_id","description":"Time off policy ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Deletion successful","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"deleteV2TimeOffPoliciesTimeOffPolicyId"}},"/v2/time_off_policies/{time_off_policy_id}/archive":{"post":{"summary":"Archive time off policy","description":"Archives the time off policy.\n\nReturns the archived time off policy.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"time_off_policy_id","description":"Time off policy ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A time off policy","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"postV2TimeOffPoliciesTimeOffPolicyIdArchive"}},"/v2/time_off_policies/{time_off_policy_id}/restore":{"post":{"summary":"Restore time off policy","description":"Restores an archived time off policy.\n\nThe policy name will be appended with a restoration date suffix.\n\nReturns the restored time off policy.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"time_off_policy_id","description":"Time off policy ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A time off policy","schema":{"type":"object","properties":{"time_off_policy":{"$ref":"#/definitions/TimeOffPolicy"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_policies"],"operationId":"postV2TimeOffPoliciesTimeOffPolicyIdRestore"}},"/v2/time_off_requests/{time_off_request_id}":{"get":{"summary":"Get time off request","description":"Returns the time off request with the given ID.\n\nIncludes time_off_request_days with per-day breakdown of dates, amounts, and holiday/paid status.\n\nAlso includes balance_preview showing the policy balance impact.\n","produces":["application/json"],"parameters":[{"in":"path","name":"time_off_request_id","description":"Time off request ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A time off request","schema":{"type":"object","properties":{"time_off_request":{"$ref":"#/definitions/TimeOffRequest"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"getV2TimeOffRequestsTimeOffRequestId"},"delete":{"summary":"Delete time off request","description":"Deletes the time off request.\n\nApproved or paid time off requests cannot be deleted.\n","produces":["application/json"],"parameters":[{"in":"path","name":"time_off_request_id","description":"Time off request ID","type":"integer","format":"int32","required":true}],"responses":{"204":{"description":"Deletion successful","schema":{"type":"object","properties":{"time_off_request":{"$ref":"#/definitions/TimeOffRequest"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"deleteV2TimeOffRequestsTimeOffRequestId"},"put":{"summary":"Update time off request","description":"Updates a time off request's details (dates, message, days, policy).\n\nThis is a full replacement — all fields must be provided, including time_off_request_days with one entry per date (amount_used in seconds, typically 28800 for an 8-hour day).\n\nApproved, paid, or partially paid requests cannot be edited.\n\nIf the request was denied, updating it automatically resubmits it (status → submitted).\n\nIf the policy does not require approval, the request is auto-approved on update.\n\nSet require_no_shift_overlap to true to reject the update if it overlaps with scheduled attendance shifts.\n\nremove_shifts deletes overlapping attendance shifts only when the policy does not require approval (the request is auto-approved on update). When the policy requires approval, this field is ignored — use the status endpoint to remove shifts at approval time instead.\n\nNote: starts_at and stops_at are interpreted as wall-clock times in the user's configured timezone (any timezone offset in the value is ignored). When all_day is true, times are normalized to 09:00–17:00.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"time_off_request_id","description":"Time off request ID","type":"integer","format":"int32","required":true},{"name":"putV2TimeOffRequestsTimeOffRequestId","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TimeOffRequestsTimeOffRequestId"}}],"responses":{"200":{"description":"The updated time off request","schema":{"type":"object","properties":{"time_off_request":{"$ref":"#/definitions/TimeOffRequest"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"putV2TimeOffRequestsTimeOffRequestId"}},"/v2/time_off_requests/{time_off_request_id}/status":{"put":{"summary":"Update time off request status","description":"Approves or denies the time off request.\n\nAllowed transitions: submitted → approved, submitted → denied, approved → denied, denied → approved.\n\nPaid or partially paid time off requests cannot be modified through this endpoint.\n\nWhen approving, use remove_shifts to automatically remove overlapping attendance shifts. This parameter is ignored when denying.\n\nA response (reason) is required when denying a request.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"time_off_request_id","description":"Time off request ID","type":"integer","format":"int32","required":true},{"name":"putV2TimeOffRequestsTimeOffRequestIdStatus","in":"body","required":true,"schema":{"$ref":"#/definitions/putV2TimeOffRequestsTimeOffRequestIdStatus"}}],"responses":{"200":{"description":"The updated time off request","schema":{"type":"object","properties":{"time_off_request":{"$ref":"#/definitions/TimeOffRequest"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_off_requests"],"operationId":"putV2TimeOffRequestsTimeOffRequestIdStatus"}},"/v2/users/me":{"get":{"summary":"Get current user","description":"Returns the authenticated user.\n","produces":["application/json"],"responses":{"200":{"description":"A user","schema":{"type":"object","properties":{"user":{"$ref":"#/definitions/User"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["users"],"operationId":"getV2UsersMe"}},"/v2/users/me/webhooks":{"post":{"summary":"Create me webhook","description":"Creates a new webhook subscription for the me. After creation, Hubstaff will send a POST request to your `target_url` with an empty body and an `X-Hook-Secret` header.\n\nYour endpoint must respond with HTTP 200 and echo the same `X-Hook-Secret` header to verify ownership.\n\nStore the secret value - you'll need it to activate the webhook.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"name":"postV2UsersMeWebhooks","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2UsersMeWebhooks"}}],"responses":{"201":{"description":"Create me webhook"}},"tags":["webhooks"],"operationId":"postV2UsersMeWebhooks"}},"/v2/users/{user_id}":{"get":{"summary":"Get user","description":"Returns the user with the given ID.\n","produces":["application/json"],"parameters":[{"in":"path","name":"user_id","description":"User ID","type":"integer","format":"int32","required":true}],"responses":{"200":{"description":"A user","schema":{"type":"object","properties":{"user":{"$ref":"#/definitions/User"}}}},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["users"],"operationId":"getV2UsersUserId"}},"/v2/users/{user_id}/time_entries":{"post":{"summary":"Create time entry","description":"Creates a new time entry.\n\nReturns {success: true} on success.\n\nNote: Manual time entries that require approval cannot be added through the API.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"user_id","type":"integer","format":"int32","required":true},{"name":"postV2UsersUserIdTimeEntries","in":"body","required":true,"schema":{"$ref":"#/definitions/postV2UsersUserIdTimeEntries"}}],"responses":{"201":{"description":"Time entry created"},"400":{"description":"Invalid parameters","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"401":{"description":"Unauthorized","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"403":{"description":"API access is only for organizations on an active plan","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"404":{"description":"Could not find record","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}},"429":{"description":"Rate limit exceeded","schema":{"$ref":"#/definitions/Hubstaff_Public_V2_Entities_Error"}}},"tags":["time_entries"],"operationId":"postV2UsersUserIdTimeEntries"}},"/v2/webhooks/{webhook_id}":{"get":{"summary":"Get webhook","description":"Returns the details of a specific webhook including its status, events, and target URL.","produces":["application/json"],"parameters":[{"in":"path","name":"webhook_id","description":"Webhook ID","type":"string","required":true}],"responses":{"200":{"description":"Get webhook"}},"tags":["webhooks"],"operationId":"getV2WebhooksWebhookId"},"delete":{"summary":"Delete webhook","description":"Permanently deletes a webhook. The webhook will stop receiving events immediately.","produces":["application/json"],"parameters":[{"in":"path","name":"webhook_id","description":"Webhook ID","type":"string","required":true}],"responses":{"204":{"description":"Delete webhook"}},"tags":["webhooks"],"operationId":"deleteV2WebhooksWebhookId"}},"/v2/webhooks/{webhook_id}/activate":{"post":{"summary":"Activate webhook","description":"Activates a webhook after endpoint verification.\n\nYou must include the `X-Hook-Secret` header with the secret value you received during the verification step.\n\nOnce activated, the webhook will start receiving events.\n","produces":["application/json"],"consumes":["application/json"],"parameters":[{"in":"path","name":"webhook_id","description":"Webhook ID","type":"string","required":true}],"responses":{"201":{"description":"Activate webhook"}},"tags":["webhooks"],"operationId":"postV2WebhooksWebhookIdActivate"}}},"definitions":{"putV2AttendanceSchedulesAttendanceScheduleId":{"type":"object","properties":{"start_date":{"type":"string","format":"date","description":"Attendance Schedule Start Date"},"start_time":{"type":"string","description":"Attendance Schedule Start Time, 24 hour time in 30 minutes intervals (h:mm)"},"use_time_zone":{"type":"string","description":"What time zone to use","enum":["organization","user"]},"duration":{"type":"integer","format":"int32","description":"Shift duration in seconds","minimum":1800,"maximum":86400},"minimum_time":{"type":"integer","format":"int32","description":"Minimum shift time in seconds"},"repeat_schedule":{"type":"string","description":"Shift repetition schedule","enum":["never","weekly","bi_weekly"]},"repeat_until":{"type":"string","format":"date","description":"Shift end date"},"weekdays":{"type":"array","description":"Shift repetition week days","items":{"type":"string","enum":["sun","mon","tue","wed","thu","fri","sat"]}},"excluded_dates":{"type":"array","description":"Excluded dates from the repetition schedule","items":{"type":"string","format":"date"}},"add_excluded_dates":{"type":"array","description":"Excluded dates to add","items":{"type":"string","format":"date"}},"remove_excluded_dates":{"type":"array","description":"Excluded dates to remove","items":{"type":"string","format":"date"}}},"description":"Update attendance schedule"},"AttendanceSchedule":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Attendance schedule ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"start_date":{"type":"string","format":"date","description":"Shift start date"},"use_time_zone":{"type":"string","description":"What time zone to use"},"start_time":{"type":"string","format":"date-time","description":"Shift start time (only hh:mm:ss"},"duration":{"type":"integer","format":"int32","description":"Shift duration"},"minimum_time":{"type":"integer","format":"int32","description":"Minimum shift time"},"repeat_schedule":{"type":"string","description":"Shift repetition schedule"},"repeat_until":{"type":"string","format":"date","description":"Shift end date"},"weekdays":{"type":"array","items":{"type":"string"},"description":"Shift repetition week days"},"excluded_dates":{"type":"array","items":{"type":"string","format":"date"},"description":"Excluded dates from the repetition schedule"}},"required":["id","user_id","organization_id","created_at","updated_at","start_date","use_time_zone","start_time","duration","minimum_time","repeat_schedule"],"description":"AttendanceSchedule model"},"Hubstaff_Public_V2_Entities_Error":{"type":"object","properties":{"code":{"type":"string","description":"Legacy string error code for backward compatibility"},"error_code":{"type":"integer","format":"int32","description":"Numeric error code for i18n/programmatic handling. Ranges: 10000-10999 (Auth), 11000-11999 (Validation), 12000-12999 (Resource), 13000-13999 (Rate Limit), 14000-14999 (Time Entry/Activity), 15000-15999 (System). See GET /v2/error_codes for full reference."},"error":{"type":"string","description":"Human-readable error message"}},"required":["code","error"],"description":"Hubstaff_Public_V2_Entities_Error model"},"BudgetHistory":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"the budget history ID"},"project_id":{"type":"integer","format":"int64","description":"The project ID the budget is for"},"client_id":{"type":"integer","format":"int64","description":"The client ID the budget is for"},"budget":{"$ref":"#/definitions/Budget"},"start_date":{"type":"string","format":"date","description":"The start date of the budget"},"stop_date":{"type":"string","format":"date","description":"The stop date of the budget"},"spent":{"type":"number","format":"float","description":"The amount spent. Either cost or hours depending on the budget type"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","budget","spent","created_at","updated_at"],"description":"BudgetHistory model"},"Budget":{"type":"object","properties":{"type":{"type":"string","description":"The budget type"},"rate":{"type":"string","description":"The budget rate type"},"cost":{"type":"number","format":"float","description":"The budget cost limit"},"hours":{"type":"number","format":"float","description":"The budget hours limit"},"start_date":{"type":"string","format":"date","description":"The budget start date"},"alerts":{"type":"object","properties":{"near_limit":{"type":"integer","format":"int32","description":"The budget near limit percentage"}},"required":["near_limit"]},"recurrence":{"type":"string","description":"The budget recurrence frequency"},"include_non_billable":{"type":"boolean","description":"Whether the budget includes non-billable time"}},"required":["type","rate","alerts","include_non_billable"]},"Client":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Client ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"name":{"type":"string","description":"Client Name"},"emails":{"type":"array","items":{"type":"string"},"description":"Emails to default send invoices to"},"phone":{"type":"string","description":"Phone Number"},"address":{"type":"string","description":"Address"},"project_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"List of projects"},"inherit_invoice_notes":{"type":"boolean","description":"True if the organizations invoice notes will be used"},"invoice_notes":{"type":"string","description":"Notes (Shown on invoices)"},"inherit_net_terms":{"type":"boolean","description":"True if the organizations net terms will be used"},"net_terms":{"type":"string","description":"Amount of days the invoice must be paid once created"},"status":{"type":"string","description":"Client status"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"budget":{"$ref":"#/definitions/Budget"},"metadata":{"type":"object","description":"Metadata attached to the client"}},"required":["id","organization_id","name","emails","phone","address","project_ids","status","created_at","updated_at","metadata"],"description":"Client model"},"putV2ClientsClientId":{"type":"object","properties":{"name":{"type":"string","description":"The new name of the client"},"emails":{"type":"array","description":"The new emails for the client","items":{"type":"string"}},"phone":{"type":"string","description":"Client phone number"},"address":{"type":"string","description":"Client address"},"project_ids":{"type":"array","description":"Client projects","items":{"type":"integer","format":"int32"}},"invoice_notes":{"type":"string","description":"Notes (Shown on invoices)"},"net_terms":{"type":"integer","format":"int32","description":"Amount of days the invoice must be paid once created"},"status":{"type":"string","description":"Client status.","enum":["active","archived"]},"metadata":{"type":"array","description":"Client metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}},"budget":{"type":"object","description":"Budget configuration","properties":{"type":{"type":"string","description":"The method for controlling the budget","enum":["cost","hours"]},"rate":{"type":"string","description":"When budget type is cost, specifies which rate to use.","enum":["bill_rate","pay_rate"],"default":"bill_rate"},"cost":{"type":"number","format":"float","description":"When budget type is cost. defines the cost limit."},"hours":{"type":"number","format":"float","description":"When budget type is hours. defines the hours limit."},"start_date":{"type":"string","format":"date","description":"The start date for the budget."},"recurrence":{"type":"string","description":"If given, makes budget reset on a monthly basis","enum":["monthly"]},"include_non_billable":{"type":"boolean","description":"Whether the budget includes non billable time","default":true},"alerts":{"type":"object","properties":{"near_limit":{"type":"integer","format":"int32","description":"The percentage threshold for notifying when approaching the budget."}}}},"required":["type"]}},"description":"Update client"},"putV2ClientsClientIdUpdateMetadata":{"type":"object","properties":{"metadata":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["metadata"],"description":"Update client metadata"},"SmartNotificationRule":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Smart notification rule ID"},"name":{"type":"string","description":"Smart notification rule name"},"enabled":{"type":"boolean","description":"Indicates whether the smart notification rule is enabled"},"frequency":{"type":"string","enum":["hourly","daily","weekly","monthly"],"description":"Determines how often the conditions for notifications are checked"},"metric_type":{"type":"string","enum":["work_time","core_time","non_core_time","unproductive_time","activity","suspicious_activity","unusual_activity","time_spent_in_tool","idle_time","idle_time_percent","manual_time","manual_time_percent"],"description":"Metric type to be monitored"},"condition":{"type":"string","enum":["above","below"],"description":"Comparison condition for triggering the notification"},"condition_value":{"type":"number","format":"float","description":"Rule threshold value (in seconds if metric type is work_time or time_spent_in_tool)"},"time_unit":{"type":"string","enum":["per_hour","per_day","per_week","per_month"],"description":"Duration of time over which the rule is evaluated. It specifies the length of the observation period"},"target":{"type":"object","description":"Who will be monitored by the rule"},"recipients":{"type":"object","description":"Who will receive the notifications when the rule conditions are met"},"deliver_by":{"type":"array","items":{"type":"string","enum":["email","slack","insights"]},"description":"List of delivery methods for sending notifications. By default, `insights` is always included"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","name","enabled","frequency","metric_type","condition","condition_value","target","recipients","deliver_by","created_at","updated_at"],"description":"SmartNotificationRule model"},"putV2InsightsSmartNotificationRulesRuleId":{"type":"object","properties":{"name":{"type":"string","description":"Rule name"},"frequency":{"type":"string","description":"Determines how often the conditions for notifications are checked","enum":["hourly","daily","weekly","monthly"]},"metric_type":{"type":"string","description":"Metric type to be monitored","enum":["work_time","core_time","non_core_time","unproductive_time","activity","suspicious_activity","unusual_activity","time_spent_in_tool","idle_time","idle_time_percent","manual_time","manual_time_percent"]},"condition":{"type":"string","description":"Rule condition","enum":["above","below"]},"condition_value":{"type":"number","format":"float","description":"Rule threshold value (in seconds if metric type is work_timeor time_spent_in_tool)"},"target":{"type":"object","properties":{"all_members":{"type":"boolean","description":"Monitor all members","default":false},"team_ids":{"type":"array","description":"Monitor specific teams","items":{"type":"integer","format":"int32"}},"user_ids":{"type":"array","description":"Monitor specific users","items":{"type":"integer","format":"int32"}}}},"recipients":{"type":"object","description":"Who will receive the notifications","properties":{"roles":{"type":"array","description":"The recipient roles (e.g., [\"owner\"])","items":{"type":"string"}},"user_ids":{"type":"array","description":"The recipient members","items":{"type":"integer","format":"int32"}}}},"deliver_by":{"type":"array","description":"List of delivery methods for sending notifications","items":{"type":"string","enum":["email","slack","insights"]}},"time_unit":{"type":"string","description":"Duration of time over which the rule is evaluated","enum":["per_hour","per_day","per_week","per_month"]}},"description":"Update smart notification rule"},"patchV2InsightsSmartNotificationRulesRuleIdToggleStatus":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Specifies the new status of the smart notification rule (true for enabled, false for disabled)"}},"required":["enabled"],"description":"Toggle smart notification rule"},"IntegrationProjectLink":{"type":"object","properties":{"project_id":{"type":"integer","format":"int64","description":"Project ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"remote_id":{"type":"string","description":"Integration remote ID"},"remote_alternate_id":{"type":"string","description":"Integration remote alternate ID"},"status":{"type":"string","description":"Status"},"disabled_reason":{"type":"string","description":"Disabled reason"},"integration_id":{"type":"integer","format":"int64","description":"Integration ID"},"integration_type":{"type":"string","description":"Integration type"},"integration_name":{"type":"string","description":"Integration name"},"integration_status":{"type":"string","description":"Integration status"},"integration_disabled_reason":{"type":"string","description":"Disabled reason"}},"required":["project_id","created_at","updated_at","remote_id","remote_alternate_id","status","integration_id","integration_type","integration_name","integration_status"],"description":"IntegrationProjectLink model"},"IntegrationUserLink":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"remote_id":{"type":"string","description":"Integration remote ID"},"status":{"type":"string","description":"Status"},"connected":{"type":"boolean","description":"True if the user has authenticated with the remote system"},"disabled_reason":{"type":"string","description":"Disabled reason"},"integration_id":{"type":"integer","format":"int64","description":"Integration ID"},"integration_type":{"type":"string","description":"Integration type"},"integration_name":{"type":"string","description":"Integration name"},"integration_status":{"type":"string","description":"Integration status"},"integration_disabled_reason":{"type":"string","description":"Disabled reason"}},"required":["user_id","created_at","updated_at","remote_id","status","connected","integration_id","integration_type","integration_name","integration_status"],"description":"IntegrationUserLink model"},"Task":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Task id"},"integration_id":{"type":"integer","format":"int64","description":"Integration ID"},"status":{"type":"string","description":"Task status"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"project_type":{"type":"string","description":"Project type"},"summary":{"type":"string","description":"Task summary"},"details":{"type":"string","description":"Task details"},"remote_id":{"type":"string","description":"Remote ID of the task in the external system"},"remote_alternate_id":{"type":"string","description":"Alternate Remote ID of the task in the external system"},"lock_version":{"type":"integer","format":"int32","description":"lock version"},"assignee_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Assignee user IDs for this task"},"pay_rate":{"type":"number","format":"float","description":"Task pay rate"},"bill_rate":{"type":"number","format":"float","description":"Task bill rate"},"metadata":{"type":"object","description":"Metadata attached to the task"},"completed_at":{"type":"string","format":"date-time","description":"Date and time task was completed"},"due_at":{"type":"string","format":"date-time","description":"Date and time task is due"},"created_at":{"type":"string","format":"date-time","description":"Date and time task was created"},"updated_at":{"type":"string","format":"date-time","description":"Date and time task was last updated"},"global_todo_id":{"type":"integer","format":"int64","description":"Global To-Do ID"}},"required":["id","status","project_id","project_type","summary","assignee_ids","metadata","created_at","updated_at"],"description":"Task model"},"Invite":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Invite ID"},"email":{"type":"string","description":"Invite email"},"invited_by_id":{"type":"integer","format":"int64","description":"User ID for who created the invite"},"pay_rate":{"type":"string","description":"Pay rate"},"project_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Projects to add the user to once invite is accepted"},"role":{"type":"string","description":"The role they will be added as once accepted"},"status":{"type":"string","description":"Invite status"},"accepted_at":{"type":"string","format":"date-time","description":"When the invite was accepted"},"trackable":{"type":"boolean","description":"Whether the user can track time"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","email","invited_by_id","project_ids","role","status","created_at","updated_at"],"description":"Invite model"},"putV2InvitesInviteId":{"type":"object","properties":{"role":{"type":"string","description":"The role to invite the user as","enum":["organization_manager","project_manager","project_user","project_viewer"]},"pay_rate":{"type":"number","format":"float","description":"A pay rate to set for the user"},"project_ids":{"type":"integer","format":"int32","description":"List of project IDs to add the user to after they accept the invite"},"trackable":{"type":"boolean","description":"Whether the user can track time"}},"description":"Update invite"},"JobSite":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Job Site ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"name":{"type":"string","description":"Job site name"},"status":{"type":"string","description":"Job site status"},"address":{"type":"string","description":"Job site address"},"latitude":{"type":"number","description":"Job site latitude"},"longitude":{"type":"number","description":"Job site longitude"},"radius":{"type":"integer","format":"int32","description":"Job site radius"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"last_visited_at":{"type":"string","format":"date-time","description":"Last visit time"},"rules":{"$ref":"#/definitions/JobSiteRule"}},"required":["id","organization_id","name","status","address","latitude","longitude","radius","created_at","updated_at","last_visited_at"],"description":"JobSite model"},"JobSiteRule":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Job Site Rule ID"},"enter_action":{"type":"string","description":"The enter action"},"exit_action":{"type":"string","description":"The exit action"},"user_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"List of user IDs to set for this rule"},"team_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"List of team IDs to set for this rule"},"start_project_id":{"type":"integer","format":"int64","description":"Project ID to auto start when enter_action is set to track"},"start_task_id":{"type":"integer","format":"int64","description":"Project ID to auto start when enter_action is set to track"}},"required":["id","enter_action","exit_action","user_ids","team_ids"]},"putV2JobSitesJobSiteId":{"type":"object","properties":{"name":{"type":"string","description":"Updated job site name"},"address":{"type":"string","description":"Updated job site address"},"latitude":{"type":"number","format":"double","description":"Updated job site longitude"},"longitude":{"type":"number","format":"double","description":"Updated job site latitude"},"radius":{"type":"integer","format":"int32","description":"Updated job site radius","minimum":50,"maximum":3000},"status":{"type":"string","description":"The job site status.","enum":["active","archived"]}},"description":"Update job site"},"putV2JobSitesJobSiteIdUpdateRules":{"type":"object","properties":{"rules":{"type":"array","description":"A set of rule updates","items":{"type":"object","properties":{"id":{"type":"integer","format":"int32","description":"The rule ID number"},"enter_action":{"type":"string","description":"The enter action","enum":["none","track","notify"]},"exit_action":{"type":"string","description":"The exit action","enum":["none","track","notify"]},"user_ids":{"type":"array","description":"List of user IDs to set for this rule (replaces other users)","items":{"type":"integer","format":"int32"}},"add_user_ids":{"type":"array","description":"List of user IDs to add to this rule","items":{"type":"integer","format":"int32"}},"remove_user_ids":{"type":"array","description":"List of user IDs to remove from this rule","items":{"type":"integer","format":"int32"}},"team_ids":{"type":"array","description":"List of team IDs to set for this rule (replaces other teams)","items":{"type":"integer","format":"int32"}},"add_team_ids":{"type":"array","description":"List of team IDs to add to this rule","items":{"type":"integer","format":"int32"}},"remove_team_ids":{"type":"array","description":"List of team IDs to remove from this rule","items":{"type":"integer","format":"int32"}},"start_project_id":{"type":"integer","format":"int32","description":"Project ID to auto start when enter_action is set to track, or none to clear\n"}},"required":["id"]}}},"required":["rules"],"description":"Update job site rules"},"putV2JobSitesJobSiteIdAddRules":{"type":"object","properties":{"rules":{"type":"array","description":"A rule configuration","items":{"type":"object","properties":{"enter_action":{"type":"string","description":"The enter action","enum":["none","track","notify"],"default":"none"},"exit_action":{"type":"string","description":"The exit action","enum":["none","track","notify"],"default":"none"},"user_ids":{"type":"array","description":"List of user IDs to set for this rule","items":{"type":"integer","format":"int32"}},"team_ids":{"type":"array","description":"List of team IDs to set for this rule","items":{"type":"integer","format":"int32"}},"start_project_id":{"type":"integer","format":"int32","description":"Project ID to auto start when enter_action is set to track"}}}}},"required":["rules"],"description":"Add job site rules"},"putV2JobSitesJobSiteIdRemoveRules":{"type":"object","properties":{"rule_ids":{"type":"array","description":"The rule ids to remove","items":{"type":"integer","format":"int32"}}},"required":["rule_ids"],"description":"Remove job site rules"},"LimitHistory":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"the limit history ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"limit_type":{"type":"string","description":"The type of limit history"},"start_date":{"type":"string","format":"date","description":"The start date of limit history"},"stop_date":{"type":"string","format":"date","description":"The end date of limit history"},"start_time":{"type":"string","format":"date-time","description":"The start time of limit history"},"stop_time":{"type":"string","format":"date-time","description":"The end time of limit history"},"total":{"type":"integer","format":"int32","description":"How much time is allowed to track (seconds)"},"used":{"type":"integer","format":"int32","description":"How much time is tracked (seconds)"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","user_id","limit_type","start_date","stop_date","start_time","stop_time","total","created_at","updated_at"],"description":"LimitHistory model"},"Note":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Note ID"},"date":{"type":"string","format":"date","description":"Organization date"},"time_slot":{"type":"string","format":"date-time","description":"Time slot"},"recorded_at":{"type":"string","format":"date-time","description":"Date and time note was recorded in the time slot"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"note":{"type":"string","description":"Note description"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"}},"required":["id","date","time_slot","recorded_at","created_at","updated_at","note","user_id","project_id","task_id"],"description":"Note model"},"postV2Organizations":{"type":"object","properties":{"name":{"type":"string","description":"The organization name"},"addons":{"type":"array","description":"A set of addons to enable on the organization","items":{"type":"string","enum":["timesheet_approvals","monitor_user_activity","team_payments","invoices","projects","work_orders","location_tracking","job_sites","attendance_schedules","time_off","achievements","teams","track_profitability","expenses","work_breaks","insights","embedded_tasks","retention","corporate_app","more_screenshots"]}},"industry":{"type":"string","description":"The industry to associate with the organization","default":"Other"}},"required":["name"],"description":"Create organization"},"Organization":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Organization ID"},"name":{"type":"string","description":"Organization name"},"status":{"type":"string","description":"Organization status"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"member_profile_fields":{"type":"array","items":{"$ref":"#/definitions/MemberProfileFields"}},"metadata":{"type":"object","description":"Metadata attached to the organization"},"invite_url":{"type":"string","format":"uri","description":"Invite URL"}},"required":["id","name","status","created_at","updated_at","member_profile_fields","metadata","invite_url"],"description":"Organization model"},"MemberProfileFields":{"type":"object","properties":{"key":{"type":"string","description":"The identifier for the custom field. This is used on the organization members endpoint."},"type":{"type":"string","description":"The type of the custom field"},"title":{"type":"string","description":"The label for the custom field"},"created_on":{"type":"string","format":"date","description":"When the custom field was created"},"created_by_id":{"type":"integer","format":"int64","description":"User ID who created the custom field"}},"required":["key","type","title","created_on","created_by_id"]},"putV2OrganizationsOrganizationIdUpdateMembers":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"role":{"type":"string","description":"Role for the user.","enum":["user","manager","owner","remove"]},"trackable":{"type":"boolean"},"view_only":{"type":"boolean","description":"View-only member flag."},"first_name":{"type":"string"},"last_name":{"type":"string"},"payment":{"type":"object","properties":{"rate_type":{"type":"string","enum":["hourly","fixed_amount"]},"pay_rate":{"type":"number","format":"float","description":"A pay rate to set for the user, used for 'hourly' or 'fixed_amount' rate_types"},"pay_period":{"type":"string","description":"A pay period to set for the user. Required if timesheet_approval is true","enum":["none","weekly","twice_per_month","biweekly","monthly"]},"timesheet_approval":{"type":"boolean","description":"Require timesheet approval for the user"},"period_week_day":{"type":"integer","format":"int32","description":"The day of the week when the pay period starts where 0 is Monday and Sunday is 6, required if pay_period is weekly or biweekly","minimum":0,"maximum":6},"period_start_day":{"type":"integer","format":"int32","description":"The day of the month when the pay period starts, required if pay_period is monthly or twice_per_month","minimum":1,"maximum":31},"period_second_start_day":{"type":"integer","format":"int32","description":"The day of the month when the second pay period starts, required if pay_period is twice_per_month. Use 31 to specify the last day of the month","minimum":1,"maximum":31}}},"limits":{"type":"object","properties":{"limits_by_shifts":{"type":"boolean"},"daily_limit":{"type":"number","format":"float","description":"The daily limit, number of hours"},"weekly_limit":{"type":"number","format":"float","description":"The weekly limit, number of hours"},"expected_per_week":{"type":"number","format":"float","description":"The expected weekly limit, number of hours"},"current_daily_limit":{"type":"number","format":"float","description":"Overrides the daily limit for today"},"current_weekly_limit":{"type":"number","format":"float","description":"Overrides the weekly limit for this week"},"dow_limit":{"type":"array","description":"The days when member is allowed to track time","items":{"type":"string","enum":["sun","mon","tue","wed","thu","fri","sat"]}},"dow_expected":{"type":"array","description":"The days when member is expected to track time","items":{"type":"string","enum":["sun","mon","tue","wed","thu","fri","sat"]}}}},"metadata":{"type":"array","description":"Member metadata. See the update_members endpoint for further details","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}},"projects":{"type":"array","items":{"type":"object","properties":{"project_id":{"type":"integer","format":"int32","description":"Project ID"},"role":{"type":"string","description":"User role in project","enum":["viewer","user","manager","remove"]}},"required":["project_id","role"]}}},"required":["user_id"]}}},"required":["members"],"description":"Update organization members"},"putV2OrganizationsOrganizationIdUpdateMetadata":{"type":"object","properties":{"metadata":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["metadata"],"description":"Update organization metadata"},"Activity":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Activity ID"},"date":{"type":"string","format":"date","description":"Organization date"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"time_slot":{"type":"string","format":"date-time","description":"Time slot"},"starts_at":{"type":"string","format":"date-time","description":"Date and time tracking was started within time slot"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"keyboard":{"type":"integer","format":"int32","description":"The amount of time the keyboard was active within the time worked in seconds"},"mouse":{"type":"integer","format":"int32","description":"The amount of time the mouse was active within the time worked in seconds"},"overall":{"type":"integer","format":"int32","description":"The amount of time the keyboard or mouse was active within the time worked in seconds"},"tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds"},"input_tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds with input tracking enabled (keyboard, mouse)"},"tracks_input":{"type":"boolean","description":"Whether the source can and had input tracking enabled activities (keyboard, mouse)"},"billable":{"type":"boolean","description":"Whether the time is billable"},"paid":{"type":"boolean","description":"Whether the time has been marked paid"},"client_invoiced":{"type":"boolean","description":"Whether the time has been invoiced to a client"},"team_invoiced":{"type":"boolean","description":"Whether the time has been invoiced for a team member"},"immutable":{"type":"boolean","description":"Whether the time is locked"},"timesheet_id":{"type":"integer","format":"int64","description":"The timesheet for this activity"},"timesheet_locked":{"type":"boolean","description":"Whether the timesheet is locking this activity"},"time_type":{"type":"string","description":"The type of activity this is"},"client":{"type":"string","description":"Which client the activity originated from"}},"required":["id","date","created_at","updated_at","time_slot","starts_at","user_id","project_id","task_id","keyboard","mouse","overall","tracked","input_tracked","tracks_input","billable","paid","client_invoiced","team_invoiced","immutable","time_type","client"],"description":"Activity model"},"postV2OrganizationsOrganizationIdAttendanceSchedules":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"start_date":{"type":"string","format":"date","description":"Attendance Schedule Start Date"},"start_time":{"type":"string","description":"Attendance Schedule Start Time, 24 hour time in 30 minutes intervals (h:mm)"},"duration":{"type":"integer","format":"int32","description":"Shift duration in seconds","minimum":1800,"maximum":86400},"minimum_time":{"type":"integer","format":"int32","description":"Minimum shift time in seconds"},"use_time_zone":{"type":"string","description":"What time zone to use","enum":["organization","user"]},"repeat_schedule":{"type":"string","description":"Shift repetition schedule","enum":["never","weekly","bi_weekly"],"default":"never"},"weekdays":{"type":"array","description":"Shift repetition week days","items":{"type":"string","enum":["sun","mon","tue","wed","thu","fri","sat"]}},"repeat_until":{"type":"string","format":"date","description":"Shift end date"},"excluded_dates":{"type":"array","description":"Excluded dates from the repetition schedule","items":{"type":"string","format":"date"}}},"required":["user_id","start_date","start_time","duration","minimum_time","use_time_zone","repeat_schedule"],"description":"Create attendance schedule"},"AttendanceShift":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Attendance shift ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"attendance_schedule_id":{"type":"integer","format":"int64","description":"Attendance schedule ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"date":{"type":"string","format":"date","description":"Shift date"},"start_time":{"type":"string","format":"date-time","description":"Shift start time"},"duration":{"type":"integer","format":"int32","description":"Shift duration"},"minimum_time":{"type":"integer","format":"int32","description":"Minimum shift time"},"status":{"type":"string","description":"Shift status"},"actual_start_time":{"type":"string","format":"date-time","description":"Actual shift start time"},"actual_stop_time":{"type":"string","format":"date-time","description":"Actual shift stop time"},"actual_duration":{"type":"integer","format":"int32","description":"Shift actual duration"}},"required":["id","user_id","organization_id","attendance_schedule_id","created_at","updated_at","date","start_time","duration","minimum_time","status","actual_start_time","actual_stop_time","actual_duration"],"description":"AttendanceShift model"},"ClientInvoice":{"type":"object","properties":{"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"status":{"type":"string","description":"Invoice status"},"author_id":{"type":"integer","format":"int64","description":"The user who created the invoice"},"number":{"type":"string","description":"Invoice number"},"po_number":{"type":"string","description":"Invoice PO number"},"notes":{"type":"string","description":"Invoice notes"},"currency":{"type":"string","description":"Invoice currency"},"grand_total":{"type":"number","format":"float","description":"Grand total"},"paid_amount":{"type":"number","format":"float","description":"Amount paid"},"tax_1":{"type":"number","format":"float","description":"Tax 1 percentage to apply to sub total for enabled line items"},"tax_2":{"type":"number","format":"float","description":"Tax 2 percentage to apply to sub total for enabled line items"},"discount":{"type":"number","format":"float","description":"Discount percentage to apply to before taxes for enabled line items"},"issue_date":{"type":"string","format":"date","description":"Invoice issue date"},"due_date":{"type":"string","format":"date","description":"Invoice due date"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"line_items":{"type":"array","items":{"$ref":"#/definitions/InvoiceLineItem"},"description":"List of invoice line items"},"viewer_url":{"type":"string","format":"uri","description":"View only URL"},"id":{"type":"integer","format":"int64","description":"Client invoice ID"},"client_id":{"type":"integer","format":"int64","description":"Client ID this invoice is for"}},"required":["organization_id","status","author_id","number","po_number","notes","currency","grand_total","paid_amount","issue_date","created_at","updated_at","viewer_url","id","client_id"],"description":"ClientInvoice model"},"InvoiceLineItem":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Invoice item id"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"description":{"type":"string","description":"Item description"},"quantity":{"type":"number","format":"float","description":"Item quantity"},"unit_price":{"type":"number","format":"float","description":"Item price"},"generated":{"type":"boolean","description":"True if this line item was generated"},"generated_data":{"type":"object","properties":{"duration":{"type":"integer","format":"int32","description":"Time worked in seconds for generated data"},"rates":{"type":"array","items":{"type":"number","format":"float"},"description":"Hourly rates used for generated data"},"grouping":{"type":"string","description":"The grouping mode used for the generated data"},"user_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"User ids included in generated data"},"project_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Project ids included in generated data"},"task_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Tasks ids included in generated data (Client invoices only)"},"start_date":{"type":"string","format":"date","description":"The start date for the time include in the generated data"},"stop_date":{"type":"string","format":"date","description":"The stop date for the time include in the generated data"}}},"tax_1":{"type":"boolean","description":"Item tax 1"},"tax_2":{"type":"boolean","description":"Item tax 2"},"discount":{"type":"boolean","description":"Item discount"}},"required":["id","created_at","updated_at","description","quantity","unit_price","tax_1","tax_2","discount"]},"postV2OrganizationsOrganizationIdClients":{"type":"object","properties":{"name":{"type":"string","description":"Client name"},"emails":{"type":"array","description":"Default emails when sending an invoice","items":{"type":"string"}},"phone":{"type":"string","description":"Client phone number"},"address":{"type":"string","description":"Client address","default":""},"project_ids":{"type":"array","description":"Project IDs associated with this client","items":{"type":"string"}},"invoice_notes":{"type":"string","description":"Notes (Shown on invoices)"},"net_terms":{"type":"integer","format":"int32","description":"Amount of days the invoice must be paid once created"},"metadata":{"type":"array","description":"Client metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}},"budget":{"type":"object","description":"Budget configuration","properties":{"type":{"type":"string","description":"The method for controlling the budget","enum":["cost","hours"]},"rate":{"type":"string","description":"When budget type is cost, specifies which rate to use.","enum":["bill_rate","pay_rate"],"default":"bill_rate"},"cost":{"type":"number","format":"float","description":"When budget type is cost. defines the cost limit."},"hours":{"type":"number","format":"float","description":"When budget type is hours. defines the hours limit."},"start_date":{"type":"string","format":"date","description":"The start date for the budget."},"recurrence":{"type":"string","description":"If given, makes budget reset on a monthly basis","enum":["monthly"]},"include_non_billable":{"type":"boolean","description":"Whether the budget includes non billable time","default":true},"alerts":{"type":"object","properties":{"near_limit":{"type":"integer","format":"int32","description":"The percentage threshold for notifying when approaching the budget."}}}},"required":["type"]}},"required":["name"],"description":"Create client"},"DailyActivity":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Daily activity ID"},"date":{"type":"string","format":"date","description":"Organization date"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"keyboard":{"type":"integer","format":"int32","description":"The amount of time (subset of tracked) the keyboard was active within the time worked in seconds"},"mouse":{"type":"integer","format":"int32","description":"The amount of time (subset of tracked) the mouse was active within the time worked in seconds"},"overall":{"type":"integer","format":"int32","description":"The amount of time (subset of tracked) the keyboard or mouse was active within the time worked in seconds"},"tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds"},"input_tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds (subset of tracked) with input tracking enabled (keyboard, mouse)"},"manual":{"type":"integer","format":"int32","description":"The amount of manual time in seconds (subset of tracked)"},"idle":{"type":"integer","format":"int32","description":"The amount of idle time in seconds (subset of tracked)"},"resumed":{"type":"integer","format":"int32","description":"The amount of resumed time in seconds (subset of tracked)"},"billable":{"type":"integer","format":"int32","description":"The amount of billable time in seconds (subset of tracked)"},"work_break":{"type":"integer","format":"int32","description":"The amount of work break time in seconds (subset of tracked)"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","date","user_id","project_id","task_id","keyboard","mouse","overall","tracked","input_tracked","manual","idle","resumed","billable","work_break","created_at","updated_at"],"description":"DailyActivity model"},"DailyToolUsage":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Daily Tool Usage ID"},"name":{"type":"string","description":"URL site or application name"},"tool_type":{"type":"string","description":"Tool type: application or url"},"date":{"type":"string","format":"date","description":"Organization date"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"tracked":{"type":"integer","format":"int32","description":"Total time in seconds spent on this tool"},"activations":{"type":"integer","format":"int32","description":"Number of times the app was brought to focus"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","name","tool_type","date","user_id","project_id","tracked","created_at","updated_at"],"description":"DailyToolUsage model"},"GlobalTodo":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Global Todo ID"},"name":{"type":"string","description":"Global Todo name"},"status":{"type":"string","description":"Global Todo status"},"add_new_projects_enabled":{"type":"boolean","description":"True if global todo is enabled for newly added projects"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","name","status","add_new_projects_enabled","created_at","updated_at"],"description":"GlobalTodo model"},"postV2OrganizationsOrganizationIdGlobalTodos":{"type":"object","properties":{"name":{"type":"string","description":"Global to-do name"},"project_ids":{"type":"array","description":"List of project_ids to add to the global to-do","items":{"type":"integer","format":"int32"}},"add_new_projects_enabled":{"type":"boolean","description":"True if global todo is enabled for newly added projects"}},"required":["name","project_ids"],"description":"Create global to-do"},"putV2OrganizationsOrganizationIdGlobalTodosId":{"type":"object","properties":{"name":{"type":"string","description":"Global to-do name"},"project_ids":{"type":"array","description":"List of project_ids to add to the global to-do","items":{"type":"integer","format":"int32"}},"add_new_projects_enabled":{"type":"boolean","description":"True if global todo is enabled for newly added projects"}},"required":["project_ids"],"description":"Update global to-do"},"Holiday":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Holiday ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"name":{"type":"string","description":"Holiday name"},"auto_add_new_users":{"type":"boolean","description":"Automatically add new members to this holiday"},"paid":{"type":"boolean","description":"Whether the holiday will be paid for members."},"amount_used":{"type":"integer","format":"int32","description":"Holiday time to be paid in seconds."},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"date":{"type":"string","format":"date","description":"Holiday date"}},"required":["id","organization_id","name","auto_add_new_users","paid","created_at","updated_at","date"],"description":"Holiday model"},"postV2OrganizationsOrganizationIdInsightsSmartNotificationRules":{"type":"object","properties":{"name":{"type":"string","description":"Rule name"},"frequency":{"type":"string","description":"Determines how often the conditions for notifications are checked","enum":["hourly","daily","weekly","monthly"]},"metric_type":{"type":"string","description":"Metric type to be monitored","enum":["work_time","core_time","non_core_time","unproductive_time","activity","suspicious_activity","unusual_activity","time_spent_in_tool","idle_time","idle_time_percent","manual_time","manual_time_percent"]},"condition":{"type":"string","description":"Rule condition","enum":["above","below"]},"condition_value":{"type":"number","format":"float","description":"Rule threshold value (in seconds if metric type is work_timeor time_spent_in_tool)"},"target":{"type":"object","description":"Who will be monitored by the rule","properties":{"all_members":{"type":"boolean","description":"Monitor all members","default":false},"team_ids":{"type":"array","description":"Monitor specific teams","items":{"type":"integer","format":"int32"}},"user_ids":{"type":"array","description":"Monitor specific users","items":{"type":"integer","format":"int32"}}}},"recipients":{"type":"object","description":"Who will receive the notifications","properties":{"roles":{"type":"array","description":"Specific roles (e.g., [\"owner\"])","items":{"type":"string"}},"user_ids":{"type":"array","description":"Specific users","items":{"type":"integer","format":"int32"}}}},"deliver_by":{"type":"array","description":"List of delivery methods for sending notifications","items":{"type":"string","enum":["email","slack","insights"]}},"time_unit":{"type":"string","description":"Duration of time over which the rule is evaluated","enum":["per_hour","per_day","per_week","per_month"]}},"required":["name","frequency","metric_type","condition","condition_value","target","recipients"],"description":"Create smart notification rule"},"SmartNotification":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Smart notification ID"},"matches":{"type":"array","items":{"$ref":"#/definitions/SmartNotificationMatch"},"description":"Matches that triggered the notification, i.e. users who violated the rule"},"total_count":{"type":"integer","format":"int32","description":"Total number of matches/users who triggered the notification"},"rule_snapshot":{"$ref":"#/definitions/SmartNotificationRuleSnapshot","description":"Snapshot of rule that triggered the notification at the time it was created"},"from":{"type":"s","format":"g","description":"Rule evaluated over a range starting on this date"},"to":{"type":"s","format":"g","description":"Rule evaluated over a range ending on this date"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"}},"required":["id","matches","total_count","rule_snapshot","from","to","created_at"],"description":"SmartNotification model"},"SmartNotificationMatch":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"value":{"type":"number","format":"float","description":"Value of the metric that the user got for the rule specified metric"}},"required":["user_id","value"]},"SmartNotificationRuleSnapshot":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Smart notification rule ID"},"name":{"type":"string","description":"Smart notification rule name"},"frequency":{"type":"string","description":"Determines how often the conditions for notifications are checked"},"metric_type":{"type":"string","description":"Metric type being monitored"},"condition":{"type":"string","description":"Comparison condition that triggered the notification"},"condition_value":{"type":"number","format":"float","description":"Rule threshold value (in seconds if metric type is work_time or time_spent_in_tool"},"time_unit":{"type":"string","description":"Duration of time over which the rule was evaluated"}},"required":["id","name","frequency","metric_type","condition","condition_value"]},"UnusualActivity":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Unusual activity ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"type":{"type":"string","description":"Unusual activity type"},"keyboard_active_seconds":{"type":"integer","format":"int64","description":"Keyboard active seconds"},"mouse_active_seconds":{"type":"integer","format":"int64","description":"Mouse active seconds"},"time_tracked_seconds":{"type":"integer","format":"int64","description":"Time tracked seconds"},"overall_active_seconds":{"type":"integer","format":"int64","description":"Overall active seconds"},"url":{"type":"string","description":"Permalink to the unusual activity page in the Web UI"},"date":{"type":"string","format":"date","description":"Organization date"},"confidence_level":{"type":"string","description":"Confidence level of the unusual activity"},"top_apps_in_use":{"type":"object","description":"Top apps in this period of time"},"top_urls_visited":{"type":"array","items":{"type":"string"},"description":"Top URLs in this period of time"},"screenshot_ids":{"type":"array","items":{"type":"integer","format":"int64"},"description":"Screenshot IDs"},"suspicious_apps":{"type":"array","items":{"type":"string"},"description":"Suspicious apps"},"starts_at":{"type":"s","format":"g","description":"Start of unusual activity"},"ends_at":{"type":"s","format":"g","description":"End of unusual activity"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","user_id","type","keyboard_active_seconds","mouse_active_seconds","time_tracked_seconds","overall_active_seconds","url","date","confidence_level","top_apps_in_use","top_urls_visited","screenshot_ids","suspicious_apps","starts_at","ends_at","created_at","updated_at"],"description":"UnusualActivity model"},"Integration":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Integration ID"},"name":{"type":"string","description":"Integration name"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"type":{"type":"string","description":"Integration type"},"last_sync_at":{"type":"string","format":"date-time","description":"Last time the integration was synced"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"status":{"type":"string","description":"Status"},"disabled_reason":{"type":"string","description":"Disabled reason"}},"required":["id","name","organization_id","type","last_sync_at","created_at","updated_at","status"],"description":"Integration model"},"postV2OrganizationsOrganizationIdInvites":{"type":"object","properties":{"email":{"type":"string","description":"The email to invite"},"role":{"type":"string","description":"The role to invite the user as","enum":["organization_manager","project_manager","project_user","project_viewer"],"default":"project_user"},"pay_rate":{"type":"number","format":"float","description":"A pay rate to set for the user"},"project_ids":{"type":"array","description":"List of project IDs to add the user to after they accept the invite","items":{"type":"integer","format":"int32"}},"trackable":{"type":"boolean","description":"Whether the user can track time","default":true},"view_only":{"type":"boolean","description":"Whether the user is in view only mode or not","default":false}},"required":["email"],"description":"Create invite"},"postV2OrganizationsOrganizationIdInvitesBulk":{"type":"object","properties":{"invites":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string","description":"The email to invite"},"role":{"type":"string","description":"The role to invite the user as","enum":["organization_manager","project_manager","project_user","project_viewer"],"default":"project_user"},"pay_rate":{"type":"number","format":"float","description":"A pay rate to set for the user"},"project_ids":{"type":"array","description":"List of project IDs to add the user to after they accept the invite","items":{"type":"integer","format":"int32"}},"trackable":{"type":"boolean","description":"Whether the user can track time","default":true},"view_only":{"type":"boolean","description":"Whether the user is in view only mode or not","default":false}},"required":["email"]}}},"required":["invites"],"description":"Bulk invite users"},"postV2OrganizationsOrganizationIdJobSites":{"type":"object","properties":{"name":{"type":"string","description":"Job site name"},"address":{"type":"string","description":"Job site address"},"latitude":{"type":"number","format":"double","description":"Job site longitude"},"longitude":{"type":"number","format":"double","description":"Job site latitude"},"radius":{"type":"integer","format":"int32","description":"Job site radius","minimum":50,"maximum":3000},"rules":{"type":"array","description":"A rule configuration","items":{"type":"object","properties":{"enter_action":{"type":"string","description":"The enter action","enum":["none","track","notify"],"default":"none"},"exit_action":{"type":"string","description":"The exit action","enum":["none","track","notify"],"default":"none"},"user_ids":{"type":"array","description":"List of user IDs to add to this rule","items":{"type":"integer","format":"int32"}},"team_ids":{"type":"array","description":"List of team IDs to add to this rule","items":{"type":"integer","format":"int32"}},"start_project_id":{"type":"integer","format":"int32","description":"Project ID to auto start when enter_action is set to track"}}}}},"required":["name","address","latitude","longitude","radius","rules"],"description":"Create job site"},"JobSiteVisit":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Job Site ID"},"job_site_id":{"type":"integer","format":"int64","description":"Job site ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"status":{"type":"string","description":"Job site visit status"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"enter_at":{"type":"string","format":"date-time","description":"Time the user entered the site"},"exit_at":{"type":"string","format":"date-time","description":"Time the user exited the site"},"last_seen_at":{"type":"string","format":"date-time","description":"The last time the user was seen at the site"}},"required":["id","job_site_id","user_id","status","created_at","updated_at","enter_at"],"description":"JobSiteVisit model"},"LastActivity":{"type":"object","properties":{"last_client_activity":{"type":"string","format":"date-time","description":"Last client activity of user"},"online":{"type":"boolean","description":"Whether we consider the user online or not"},"last_project_id":{"type":"integer","format":"int64","description":"Last project ID the user worked on"},"last_task_id":{"type":"integer","format":"int64","description":"Last task ID the user worked on"},"user_id":{"type":"integer","format":"int64","description":"User ID"}},"required":["last_client_activity","online","last_task_id","user_id"],"description":"LastActivity model"},"Location":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Location ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"time_slot":{"type":"string","format":"date-time","description":"Time slot"},"recorded_at":{"type":"string","format":"date-time","description":"Date and time the location was recorded"},"date":{"type":"string","format":"date","description":"Organization date"},"latitude":{"type":"number","format":"float","description":"Latitude"},"longitude":{"type":"number","format":"float","description":"Longitude"},"accuracy":{"type":"number","format":"float","description":"Accuracy"},"job_site_id":{"type":"integer","format":"int64","description":"Job Site ID"},"event":{"type":"string","description":"Event type (enter, exit, move"}},"required":["id","user_id","project_id","created_at","updated_at","time_slot","recorded_at","date","latitude","longitude","accuracy","event"],"description":"Location model"},"OrganizationMember":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"pay_rate":{"type":"string","description":"Pay rate"},"bill_rate":{"type":"string","description":"Bill rate"},"currency":{"type":"string","description":"Currency of pay and bill rates"},"membership_role":{"type":"string","description":"Membership role"},"membership_status":{"type":"string","description":"Membership status"},"created_at":{"type":"string","format":"date-time","description":"When the member was added to the organization/project"},"updated_at":{"type":"string","format":"date-time","description":"When the membership was last updated"},"removed_at":{"type":"string","format":"date-time","description":"When the member was removed"},"last_client_activity":{"type":"string","format":"date-time","description":"Last client activity of user"},"user":{"$ref":"#/definitions/User","description":"Related user object"},"project_members":{"type":"array","items":{"$ref":"#/definitions/NestedProjectMember"},"description":"List of project memberships"},"pay_period":{"type":"string","description":"Pay period"},"fixed_pay_rate":{"type":"string","description":"Fixed pay rate"},"period_week_day":{"type":"integer","format":"int32","description":"Pay period week day (0 monday)"},"period_start_day":{"type":"integer","format":"int32","description":"Pay period start day"},"period_second_start_day":{"type":"integer","format":"int32","description":"Second pay period start day"},"trackable":{"type":"string","description":"Whether the member has tracking enabled, disabled or implicitly disabled"},"effective_role":{"type":"string","description":"A virtual membership role that takes into account the project memberships"},"profile":{"$ref":"#/definitions/OrganizationMemberProfile"},"limits":{"$ref":"#/definitions/OrganizationMemberLimits"},"metadata":{"type":"object","description":"Metadata attached to the member"},"view_only":{"type":"boolean","description":"View-only member flag"}},"required":["user_id","membership_role","membership_status","created_at","updated_at","last_client_activity","trackable","effective_role","metadata","view_only"],"description":"OrganizationMember model"},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"User ID"},"name":{"type":"string","description":"User full name"},"first_name":{"type":"string","description":"User first name"},"last_name":{"type":"string","description":"User last name"},"email":{"type":"string","description":"User email address"},"time_zone":{"type":"string","description":"User time zone"},"ip_address":{"type":"string","description":"User last IP address"},"status":{"type":"string","description":"User status"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","name","first_name","last_name","time_zone","ip_address","status","created_at","updated_at"],"description":"User model"},"NestedProjectMember":{"type":"object","properties":{"pay_rate":{"type":"string","description":"Pay rate"},"bill_rate":{"type":"string","description":"Bill rate"},"currency":{"type":"string","description":"Currency of pay and bill rates"},"membership_role":{"type":"string","description":"Membership role"},"membership_status":{"type":"string","description":"Membership status"},"created_at":{"type":"string","format":"date-time","description":"When the member was added to the organization/project"},"updated_at":{"type":"string","format":"date-time","description":"When the membership was last updated"},"removed_at":{"type":"string","format":"date-time","description":"When the member was removed"},"last_client_activity":{"type":"string","format":"date-time","description":"Last client activity of user"},"team_id":{"type":"integer","format":"int64","description":"Team ID"},"profile":{"$ref":"#/definitions/OrganizationMemberProfile"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"project_type":{"type":"string","description":"Project type (project, work_order, work_break)"},"project_status":{"type":"string","description":"Project status (active, archived, deleted)"}},"required":["membership_role","membership_status","created_at","updated_at","last_client_activity","project_id","project_type","project_status"]},"OrganizationMemberProfile":{"type":"object","properties":{"job_title":{"type":"string","description":"The job title of the member"},"job_type":{"type":"string","description":"The job type of the member"},"location":{"type":"string","description":"The location of the member"},"employee_id":{"type":"string","description":"The employee id of the member"},"tax_info":{"type":"string","description":"The tax info of the member"},"birthday":{"type":"string","format":"date","description":"The member's birthday (YYYY-MM-DD). If the year is not available, it will be 0000"},"started_on":{"type":"string","format":"date","description":"The start date of the member"},"custom_fields":{"type":"object","description":"Custom fields"}}},"OrganizationMemberLimits":{"type":"object","properties":{"limits_by_shifts":{"type":"boolean","description":"Whether the shifts are used to set allowance limits"},"weekly_limit":{"type":"number","format":"float","description":"The hours member is allowed to work weekly"},"daily_limit":{"type":"number","format":"float","description":"The hours daily limit of the member"},"dow_limit":{"type":"array","items":{"type":"string"},"description":"The days when member is allowed to track time"},"expected_per_week":{"type":"number","format":"float","description":"The hours member is expected to work weekly"},"dow_expected":{"type":"array","items":{"type":"string"},"description":"The days when member is expected to track time"}}},"postV2OrganizationsOrganizationIdMembers":{"type":"object","properties":{"user":{"type":"object","description":"User data","properties":{"name":{"type":"string","description":"User full name (deprecated, use first_name, last_name)"},"first_name":{"type":"string","description":"User first name"},"last_name":{"type":"string","description":"User last name"},"email":{"type":"string","description":"User email"},"password":{"type":"string","description":"User password"},"require_password_change":{"type":"boolean","description":"Require user to change password on first sign in","default":true},"allow_receiving_emails":{"type":"boolean","description":"Allow user to receive Hubstaff emails","default":false}},"required":["email","password"]},"role":{"type":"string","description":"The role to add the user as","enum":["organization_manager","project_manager","project_user","project_viewer"],"default":"project_user"},"pay_rate":{"type":"number","format":"float","description":"A pay rate to set for the user"},"view_only":{"type":"boolean","description":"Create the member as view-only"},"pay_period":{"type":"string","description":"A pay period to set for the user. Required if timesheet_approval is true","enum":["none","weekly","twice_per_month","biweekly","monthly"]},"period_week_day":{"type":"integer","format":"int32","description":"The day of the week when the pay period starts where 0 is Monday and Sunday is 6, required if pay_period is weekly or biweekly","minimum":0,"maximum":6},"period_start_day":{"type":"integer","format":"int32","description":"The day of the month when the pay period starts, required if pay_period is monthly or twice_per_month","minimum":1,"maximum":31},"period_second_start_day":{"type":"integer","format":"int32","description":"The day of the month when the second pay period starts, required if pay_period is twice_per_month. Use 31 to specify the last day of the month","minimum":1,"maximum":31},"timesheet_approval":{"type":"boolean","description":"Require timesheet approval for the user","default":false},"project_ids":{"type":"array","description":"List of project IDs to add the user","items":{"type":"integer","format":"int32"}},"team_ids":{"type":"array","description":"List of team IDs to add the user","items":{"type":"integer","format":"int32"}},"metadata":{"type":"array","description":"Member metadata. See the update_members endpoint for further details","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["user"],"description":"Create organization member"},"OrganizationSeat":{"type":"object","properties":{"paid":{"type":"integer","format":"int32","description":"Number of paid seats"},"occupied":{"type":"integer","format":"int32","description":"Number of occupied seats"},"open":{"type":"integer","format":"int32","description":"Number of open seats"}},"required":["paid","occupied","open"],"description":"OrganizationSeat model"},"PassiveJobSiteVisit":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Job Site ID"},"job_site_id":{"type":"integer","format":"int64","description":"Job site ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"status":{"type":"string","description":"Job site visit status"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"enter_at":{"type":"string","format":"date-time","description":"Time the user entered the site"},"exit_at":{"type":"string","format":"date-time","description":"Time the user exited the site"},"last_seen_at":{"type":"string","format":"date-time","description":"The last time the user was seen at the site"}},"required":["id","job_site_id","user_id","status","created_at","updated_at","enter_at"],"description":"PassiveJobSiteVisit model"},"Project":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Project ID"},"name":{"type":"string","description":"Project name"},"description":{"type":"string","description":"Project description"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"status":{"type":"string","description":"Project status"},"type":{"type":"string","description":"Project type (project, work_order, work_break)"},"client_id":{"type":"integer","format":"int64","description":"Client ID"},"billable":{"type":"boolean","description":"Default state of billable attribute for new activity"},"budget":{"$ref":"#/definitions/Budget"},"metadata":{"type":"object","description":"Metadata attached to the project"},"pay_rate":{"type":"number","format":"float","description":"Default pay rate for the project"},"bill_rate":{"type":"number","format":"float","description":"Default bill rate for the project"}},"required":["id","name","created_at","updated_at","status","type","billable","metadata"],"description":"Project model"},"postV2OrganizationsOrganizationIdProjects":{"type":"object","properties":{"name":{"type":"string","description":"Project name"},"description":{"type":"string","description":"Project description"},"metadata":{"type":"array","description":"Project metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}},"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"role":{"type":"string","description":"Role for the user","enum":["viewer","user","manager"]},"pay_rate":{"type":"number","format":"float","description":"Default pay rate (will be rounded to 2 decimal places). Set to null to remove the rate"},"bill_rate":{"type":"number","format":"float","description":"Default bill rate (will be rounded to 2 decimal places). Set to null to remove the rate"}},"required":["user_id","role"]}},"client_id":{"type":"integer","format":"int32","description":"The client ID"},"billable":{"type":"boolean","description":"Whether activity logged against this project is billable by default (default is true)"},"budget":{"type":"object","description":"Budget configuration","properties":{"type":{"type":"string","description":"The method for controlling the budget","enum":["cost","hours"]},"rate":{"type":"string","description":"When budget type is cost, specifies which rate to use.","enum":["bill_rate","pay_rate"],"default":"bill_rate"},"cost":{"type":"number","format":"float","description":"When budget type is cost. defines the cost limit."},"hours":{"type":"number","format":"float","description":"When budget type is hours. defines the hours limit."},"start_date":{"type":"string","format":"date","description":"The start date for the budget."},"recurrence":{"type":"string","description":"If given, makes budget reset on a monthly basis","enum":["monthly"]},"include_non_billable":{"type":"boolean","description":"Whether the budget includes non billable time","default":true},"alerts":{"type":"object","properties":{"near_limit":{"type":"integer","format":"int32","description":"The percentage threshold for notifying when approaching the budget."}}}},"required":["type"]},"pay_rate":{"type":"number","format":"float","description":"Default pay rate (will be rounded to 2 decimal places). Set to null to remove the rate"},"bill_rate":{"type":"number","format":"float","description":"Default bill rate (will be rounded to 2 decimal places). Set to null to remove the rate"}},"required":["name"],"description":"Create project"},"Screenshot":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Screenshot ID"},"full_url":{"type":"string","description":"Screenshot URL for the full image"},"thumb_url":{"type":"string","description":"Screenshot URL for the thumbnail"},"date":{"type":"string","format":"date","description":"Organization date"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"time_slot":{"type":"string","format":"date-time","description":"Time slot"},"recorded_at":{"type":"string","format":"date-time","description":"Date and time screenshot was recorded"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"offset_x":{"type":"integer","format":"int32","description":"Screen offset X"},"offset_y":{"type":"integer","format":"int32","description":"Screen offset Y"},"width":{"type":"integer","format":"int32","description":"Screenshot width"},"height":{"type":"integer","format":"int32","description":"Screenshot height"},"screen":{"type":"integer","format":"int32","description":"Screen number"}},"required":["id","date","created_at","updated_at","time_slot","recorded_at","user_id","project_id","offset_x","offset_y","width","height","screen"],"description":"Screenshot model"},"TeamInvoice":{"type":"object","properties":{"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"status":{"type":"string","description":"Invoice status"},"author_id":{"type":"integer","format":"int64","description":"The user who created the invoice"},"number":{"type":"string","description":"Invoice number"},"po_number":{"type":"string","description":"Invoice PO number"},"notes":{"type":"string","description":"Invoice notes"},"currency":{"type":"string","description":"Invoice currency"},"grand_total":{"type":"number","format":"float","description":"Grand total"},"paid_amount":{"type":"number","format":"float","description":"Amount paid"},"tax_1":{"type":"number","format":"float","description":"Tax 1 percentage to apply to sub total for enabled line items"},"tax_2":{"type":"number","format":"float","description":"Tax 2 percentage to apply to sub total for enabled line items"},"discount":{"type":"number","format":"float","description":"Discount percentage to apply to before taxes for enabled line items"},"issue_date":{"type":"string","format":"date","description":"Invoice issue date"},"due_date":{"type":"string","format":"date","description":"Invoice due date"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"line_items":{"type":"array","items":{"$ref":"#/definitions/InvoiceLineItem"},"description":"List of invoice line items"},"viewer_url":{"type":"string","format":"uri","description":"View only URL"},"id":{"type":"integer","format":"int64","description":"Team invoice ID"},"user_id":{"type":"integer","format":"int64","description":"User ID this invoice is for"}},"required":["organization_id","status","author_id","number","po_number","notes","currency","grand_total","paid_amount","issue_date","created_at","updated_at","viewer_url","id","user_id"],"description":"TeamInvoice model"},"TeamPayment":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Payment ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"organization_id":{"type":"string","description":"Organization ID"},"start_date":{"type":"string","format":"date","description":"Start date period for team payment"},"stop_date":{"type":"string","format":"date","description":"End date period for team payment"},"blame_user_id":{"type":"integer","format":"int64","description":"User ID who made the payment"},"note":{"type":"string","description":"A note for this team payment"},"tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds"},"amount":{"type":"string","description":"Payment amount"},"currency":{"type":"string","description":"Payment currency"},"payment_source":{"type":"string","description":"Payment source type"},"paid_via_payroll":{"type":"boolean","description":"Paid via the payroll gateway"},"unpaid_amount":{"type":"string","description":"The amount left to be paid"},"payment_gateway":{"type":"string","description":"The payroll gateway used to pay"},"payment_complete":{"type":"boolean","description":"Payment completion status"},"next_payment_at":{"type":"string","format":"date-time","description":"The next time this payment will be retried"},"details":{"type":"array","items":{"$ref":"#/definitions/TeamPaymentDetail"},"description":"Team payment details"}},"required":["id","created_at","updated_at","organization_id","start_date","stop_date","tracked","amount","currency","payment_source","paid_via_payroll","details"],"description":"TeamPayment model"},"TeamPaymentDetail":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Payment detail ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"date":{"type":"string","format":"date","description":"Date for the time worked"},"tracked":{"type":"integer","format":"int32","description":"Amount of time tracked in seconds, including overtime"},"overtime_tracked":{"type":"integer","format":"int32","description":"Amount of overtime in seconds"},"pay_rate":{"type":"string","description":"The pay rate for this entry"},"overtime_pay_rate":{"type":"string","description":"The calculated overtime pay rate for this entry (regular_pay_rate * overtime_multiplier). The overtime_multiplier is defined in the organization overtime policy"},"amount":{"type":"string","description":"The amount paid for this entry"},"currency":{"type":"string","description":"The currency for the pay rate and amount"},"payment_type":{"type":"string","description":"The type of payment detail entry"},"payment_complete":{"type":"boolean","description":"Is this detail paid"}},"required":["id","user_id","date","tracked","pay_rate","amount","currency","payment_type"]},"Team":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Team ID"},"name":{"type":"string","description":"Team name"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"metadata":{"type":"object","description":"Metadata attached to the team"},"lead_options":{"type":"object","properties":{"approve_timesheets":{"type":"boolean","description":"Team leads can approve timesheets for team members"},"notifications_for_timesheets_only_leads":{"type":"boolean","description":"Only team leeds receive timesheet notifications. Requires approve_timesheets enabled."},"approve_time_off":{"type":"boolean","description":"Team leads can approve time off for team members"},"approve_manual_time":{"type":"boolean","description":"Team leads can approve manual time added for team members"},"receive_manual_time_notification":{"type":"boolean","description":"Only team leads receive manual time notifications. Requires approve_manual_time enabled."},"notifications_for_time_off_only_leads":{"type":"boolean","description":"Only team leads receive time off notifications. Requires approve_time_off enabled."},"schedule_shifts":{"type":"boolean","description":"Team leads can schedule shifts for team members"},"notifications_for_schedules_only_leads":{"type":"boolean","description":"Only team leads receive notifications for shifts. Requires schedule_shifts enabled."},"notifications_for_limits":{"type":"boolean","description":"Team leads receive weekly and daily limit notifications for team members"},"notifications_for_limits_only_leads":{"type":"boolean","description":"Team lead only receive weekly and daily limit notifications. Requires notifications_for_limits enabled."},"notifications_work_breaks":{"type":"boolean","description":"Team leads receive work break notifications for team members"},"notifications_work_breaks_only_leads":{"type":"boolean","description":"Only team leads receive work break notifications. Requires notifications_work_breaks enabled."},"manage_team_projects":{"type":"boolean","description":"Team leads can create team projects"},"manage_team_members":{"type":"boolean","description":"Team leads can edit members and their roles"},"invite_members_to_organization":{"type":"boolean","description":"Team leads can invite new members to the organization"},"notifications_for_job_sites":{"type":"boolean","description":"Team leads receive job site notifications"},"notifications_for_job_sites_only_leads":{"type":"boolean","description":"Only team leads receive job site notifications. Requires notifications_for_job_sites enabled."},"others_screenshots":{"type":"boolean","description":"View screenshots/activities for other members"},"manage_billing":{"type":"boolean","description":"Manage financials"}},"required":["approve_timesheets","notifications_for_timesheets_only_leads","approve_time_off","approve_manual_time","receive_manual_time_notification","notifications_for_time_off_only_leads","schedule_shifts","notifications_for_schedules_only_leads","notifications_for_limits","notifications_for_limits_only_leads","notifications_work_breaks","notifications_work_breaks_only_leads","manage_team_projects","manage_team_members","invite_members_to_organization","notifications_for_job_sites","notifications_for_job_sites_only_leads","others_screenshots","manage_billing"],"description":"Lead options"}},"required":["id","name","created_at","updated_at","metadata"],"description":"Team model"},"postV2OrganizationsOrganizationIdTeams":{"type":"object","properties":{"name":{"type":"string","description":"Team name"},"metadata":{"type":"array","description":"Team metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}},"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"role":{"type":"string","description":"Role for the user on associated projects","enum":["viewer","user","manager"]},"lead":{"type":"boolean","description":"Specify that the user is a team lead or not (default false)"}},"required":["user_id","role"]}},"projects":{"type":"array","items":{"type":"object","properties":{"project_id":{"type":"integer","format":"int32","description":"List of project to add to the team"}},"required":["project_id"]}},"lead_options":{"type":"object","properties":{"approve_timesheets":{"type":"boolean","description":"Team leads can approve timesheets for team members","default":true},"notifications_for_timesheets_only_leads":{"type":"boolean","description":"Only team leeds receive timesheet notifications. Requires approve_timesheets enabled.","default":false},"approve_time_off":{"type":"boolean","description":"Team leads can approve time off for team members","default":true},"approve_manual_time":{"type":"boolean","description":"Team leads can approve manual time added for team members","default":true},"receive_manual_time_notification":{"type":"boolean","description":"Only team leads receive manual time notifications. Requires approve_manual_time enabled.","default":true},"notifications_for_time_off_only_leads":{"type":"boolean","description":"Only team leads receive time off notifications. Requires approve_time_off enabled.","default":false},"schedule_shifts":{"type":"boolean","description":"Team leads can schedule shifts for team members","default":true},"notifications_for_schedules_only_leads":{"type":"boolean","description":"Only team leads receive notifications for shifts. Requires schedule_shifts enabled.","default":false},"notifications_for_limits":{"type":"boolean","description":"Team leads receive weekly and daily limit notifications for team members","default":false},"notifications_for_limits_only_leads":{"type":"boolean","description":"Team lead only receive weekly and daily limit notifications. Requires notifications_for_limits enabled.","default":false},"notifications_work_breaks":{"type":"boolean","description":"Team leads receive work break notifications for team members","default":false},"notifications_work_breaks_only_leads":{"type":"boolean","description":"Only team leads receive work break notifications. Requires notifications_work_breaks enabled.","default":false},"manage_team_projects":{"type":"boolean","description":"Team leads can create team projects","default":true},"manage_team_members":{"type":"boolean","description":"Team leads can edit members and their roles","default":true},"invite_members_to_organization":{"type":"boolean","description":"Team leads can invite new members to the organization","default":false},"notifications_for_job_sites":{"type":"boolean","description":"Team leads receive job site notifications","default":true},"notifications_for_job_sites_only_leads":{"type":"boolean","description":"Only team leads receive job site notifications. Requires notifications_for_job_sites enabled.","default":false},"others_screenshots":{"type":"boolean","description":"View screenshots/activities for other members","default":true},"manage_billing":{"type":"boolean","description":"Manage financials","default":false}}}},"required":["name"],"description":"Create team"},"TimeEditLog":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Time edit log ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"starts_at":{"type":"string","format":"date-time","description":"The start time of the final time entry"},"stops_at":{"type":"string","format":"date-time","description":"The stop time of the final time entry"},"original_starts_at":{"type":"string","format":"date-time","description":"The start time of the original time entry"},"original_stops_at":{"type":"string","format":"date-time","description":"The stop time of the original time entry"},"blame_user_id":{"type":"integer","format":"int64","description":"User ID who created the time edit log"},"user_id":{"type":"integer","format":"int64","description":"User ID to which the time edit log belongs"},"original_project_id":{"type":"integer","format":"int64","description":"Original Project ID"},"original_task_id":{"type":"integer","format":"int64","description":"Original Task ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"action":{"type":"string","description":"Action type"},"added_duration":{"type":"integer","format":"int32","description":"The number of seconds that were added due to this edit"},"deleted_duration":{"type":"integer","format":"int32","description":"The number of seconds that were deleted due to this edit"},"replaced_duration":{"type":"integer","format":"int32","description":"The number of seconds that were replaced due to this edit"},"reason":{"type":"string","description":"Reason for making the change"}},"required":["id","created_at","starts_at","stops_at","blame_user_id","user_id","action","added_duration","deleted_duration","replaced_duration","reason"],"description":"TimeEditLog model"},"TimeOffBalance":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"time_off_policy_id":{"type":"integer","format":"int64","description":"Time off policy ID"},"policy_name":{"type":"string","description":"Time off policy name"},"amount":{"type":"integer","format":"int64","description":"Balance amount in seconds"}},"required":["user_id","time_off_policy_id","policy_name","amount"],"description":"TimeOffBalance model"},"postV2OrganizationsOrganizationIdTimeOffBalances":{"type":"object","properties":{"reason":{"type":"string","description":"Reason for adjustment"},"apply_accruals":{"type":"boolean","description":"Apply annual accrual to newly assigned users","default":false},"balance_adjustments":{"type":"array","items":{"type":"object","properties":{"time_off_policy_id":{"type":"integer","format":"int32","description":"Time off policy ID"},"user_ids":{"type":"array","description":"User IDs to adjust","items":{"type":"integer","format":"int32"}},"amount":{"type":"integer","format":"int32","description":"Amount in seconds"},"modify_balance":{"type":"string","description":"Whether to add to or replace the current balance","enum":["add","replace"]}},"required":["time_off_policy_id","user_ids","amount","modify_balance"]}}},"required":["reason","balance_adjustments"],"description":"Add organization time off balance adjustments"},"TimeOffPolicy":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Time off policy ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"name":{"type":"string","description":"Time off policy name"},"created_by_id":{"type":"integer","format":"int64","description":"User ID who created the policy"},"auto_add_new_users":{"type":"boolean","description":"Automatically add new members to this policy"},"allow_negative_balances":{"type":"boolean","description":"Member can request time off before having the balance for it."},"requires_approval":{"type":"boolean","description":"When false time off requests are automatically approved."},"balance_rolls_over_annually":{"type":"boolean","description":"balance rolls over annually"},"paid":{"type":"boolean","description":"When true members will be paid for time off taken."},"status":{"type":"string","description":"Policy status"},"policy_type":{"type":"string","description":"Type of policy"},"policy_config":{"type":"object","properties":{"starting_balance":{"type":"number","format":"float","description":"Starting balance (hours)"},"hours_per":{"type":"integer","format":"int32","description":"Hours per at amount accrued"},"hours_worked":{"type":"integer","format":"int32","description":"Hours worked at amount accrued"},"maximum_to_accrue":{"type":"integer","format":"int32","description":"Maximum amount of time off that can be accrued per year. (hours)"},"hours_per_year":{"type":"integer","format":"int32","description":"Hours per year"},"hours_per_month":{"type":"number","format":"float","description":"Hours per month"},"accrual_period":{"type":"string","enum":["month","year"],"description":"Accrual period. If set to \"month\", hours_per_month is required. If set to \"year\", hours_per_year is required"},"accrual_day":{"type":"string","enum":["first_day_of_month","monthly_anniversary","last_day_of_month"],"description":"Day of the month when accrual happens"},"prorate_on_start":{"type":"boolean","description":"Whether to prorate time based on start date"}}},"membership_rules":{"type":"object","properties":{"countries":{"type":"Array[String]","description":"ISO 3166-1 alpha-2 country codes"},"employment_types":{"type":"Array[String]","description":"Employment type filters"}},"required":["countries","employment_types"]}},"required":["id","organization_id","created_at","updated_at","name","auto_add_new_users","allow_negative_balances","requires_approval","balance_rolls_over_annually","paid","status","policy_type","policy_config"],"description":"TimeOffPolicy model"},"postV2OrganizationsOrganizationIdTimeOffPolicies":{"type":"object","properties":{"name":{"type":"string","description":"Policy name"},"accrual_type":{"type":"string","enum":["none","annual","monthly","hours_worked","joined_date"]},"accrual_policy":{"type":"object","description":"Accrual configuration (depends on accrual_type)","properties":{"hours_per_year":{"type":"number","format":"float"},"hours_worked":{"type":"number","format":"float"},"hours_per":{"type":"number","format":"float"},"maximum_to_accrue":{"type":"number","format":"float","description":"Max accrual hours/year (required except none and monthly)"},"hours_per_month":{"type":"number","format":"float"},"accrual_period":{"type":"string","enum":["month","year"]},"accrual_day":{"type":"string","enum":["first_day_of_month","monthly_anniversary","last_day_of_month"]},"prorate_on_start":{"type":"boolean"},"starting_balance":{"type":"number","format":"float"}}},"allow_negative_balances":{"type":"boolean"},"requires_approval":{"type":"boolean"},"balance_rolls_over_annually":{"type":"boolean"},"paid":{"type":"boolean"},"auto_add_new_users":{"type":"boolean"},"time_off_policy_user_ids":{"type":"array","description":"User IDs to add as members","items":{"type":"integer","format":"int32"}},"membership_rules":{"type":"object","description":"Auto-membership rules","properties":{"countries":{"type":"array","description":"ISO 3166-1 alpha-2 country codes","items":{"type":"string"}},"employment_types":{"type":"array","description":"Member employment type filter","items":{"type":"string","enum":["contractor_hourly","contractor_fixed_rate","contractor_project_based","fte_hourly","fte_salary","pte_hourly","pte_salary"]}}}}},"required":["name","accrual_type","allow_negative_balances","requires_approval","balance_rolls_over_annually","paid"],"description":"Create time off policy"},"TimeOffRequest":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Time edit log ID"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"time_off_policy_id":{"type":"integer","format":"int64","description":"Time off request ID"},"status":{"type":"string","description":"Status"},"all_day":{"type":"boolean","description":"Time off request for all day (User's time zone)"},"starts_at":{"type":"string","format":"date-time","description":"Time off request start time"},"stops_at":{"type":"string","format":"date-time","description":"Time off request stop time"},"submitted_at":{"type":"string","format":"date-time","description":"Time off request submission time"},"approved_at":{"type":"string","format":"date-time","description":"Time off request approving time"},"denied_at":{"type":"string","format":"date-time","description":"Time off request denial time"},"paid":{"type":"boolean","description":"If the time off request has been paid"},"amount_used":{"type":"integer","format":"int32","description":"Time off request amount used"},"message":{"type":"string","description":"message"},"response":{"type":"string","description":"response"},"created_by_id":{"type":"integer","format":"int64","description":"User ID from creator"},"approved_by_id":{"type":"integer","format":"int64","description":"Approving User ID"},"denied_by_id":{"type":"integer","format":"int64","description":"Denying User ID"},"team_payment_detail_id":{"type":"integer","format":"int64","description":"Team payment detail ID"},"team_payment_id":{"type":"integer","format":"int64","description":"Team payment ID"},"time_off_request_days":{"$ref":"#/definitions/TimeOffRequestDay","description":"Individual work days covered by this time off request"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","user_id","time_off_policy_id","status","all_day","starts_at","stops_at","paid","amount_used","created_by_id","time_off_request_days","created_at","updated_at"],"description":"TimeOffRequest model"},"TimeOffRequestDay":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"Date of the time off day"},"amount_used":{"type":"integer","format":"int32","description":"Amount of time used in seconds for this day"},"starts_at":{"type":"string","format":"date-time","description":"Start time for this specific day"},"stops_at":{"type":"string","format":"date-time","description":"Stop time for this specific day"},"holiday":{"type":"boolean","description":"Whether this day is a holiday"},"paid":{"type":"boolean","description":"Whether this day has been paid"}},"required":["date","amount_used","starts_at","stops_at","holiday","paid"]},"postV2OrganizationsOrganizationIdTimeOffRequests":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"time_off_policy_id":{"type":"integer","format":"int32","description":"Time off policy ID"},"starts_at":{"type":"string","format":"date-time","description":"Start time (ISO 8601)"},"stops_at":{"type":"string","format":"date-time","description":"Stop time (ISO 8601)"},"message":{"type":"string","description":"Reason for time off"},"all_day":{"type":"boolean","description":"Full day request","default":true},"exclude_weekends":{"type":"boolean","default":false},"exclude_holidays":{"type":"boolean","default":false},"require_no_shift_overlap":{"type":"boolean","description":"Reject if overlapping shifts exist","default":false},"remove_shifts":{"type":"boolean","description":"Remove overlapping attendance shifts. Only honored when the policy does not require approval (auto-approved requests). For policies that require approval, use the status endpoint to remove shifts upon approval."},"time_off_request_days":{"type":"array","description":"Day allocations","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"amount_used":{"type":"integer","format":"int32","description":"Seconds"}},"required":["date","amount_used"]}}},"required":["user_id","time_off_policy_id","starts_at","stops_at","message","time_off_request_days"],"description":"Create time off request"},"Timesheet":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Timesheet ID"},"paid":{"type":"boolean","description":"Is the timesheet paid?"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"organization_id":{"type":"integer","format":"int64","description":"Organization ID"},"team_payment_id":{"type":"integer","format":"int64","description":"Team payment ID"},"team_invoice_id":{"type":"integer","format":"int64","description":"Team invoice ID"},"status":{"type":"string","description":"Status"},"keyboard":{"type":"integer","format":"int32","description":"Percentage of time keyboard was active within the time worked"},"mouse":{"type":"integer","format":"int32","description":"Percentage of time mouse was active within the time worked"},"overall":{"type":"integer","format":"int32","description":"Percentage of overall time active within the time worked"},"tracked":{"type":"integer","format":"int32","description":"Total time tracked (minutes)"},"input_tracked":{"type":"integer","format":"int32","description":"Total time worked in seconds when inputs (keyboard, mouse) were active"},"start_date":{"type":"string","format":"date","description":"Start date"},"stop_date":{"type":"string","format":"date","description":"Stop date"},"approved_on":{"type":"string","format":"date","description":"Approving date"},"submitted_on":{"type":"string","format":"date","description":"Submission date"},"approved_by_id":{"type":"integer","format":"int64","description":"Approving User ID"},"locked":{"type":"boolean","description":"Is the timesheet locked"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","paid","user_id","organization_id","status","keyboard","mouse","overall","tracked","input_tracked","start_date","stop_date","locked","created_at","updated_at"],"description":"Timesheet model"},"ToolUsage":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Tool usage ID"},"name":{"type":"string","description":"URL site or application name"},"tool_type":{"type":"string","description":"Tool type: application or url"},"date":{"type":"string","format":"date","description":"Organization date"},"time_slot":{"type":"string","format":"date-time","description":"Time slot"},"user_id":{"type":"integer","format":"int64","description":"User ID"},"project_id":{"type":"integer","format":"int64","description":"Project ID"},"task_id":{"type":"integer","format":"int64","description":"Task ID"},"tracked":{"type":"integer","format":"int32","description":"Total time in seconds spent on this tool"},"details":{"type":"array","items":{"$ref":"#/definitions/UrlActivityDetail"},"description":"URL activity details"},"activations":{"type":"integer","format":"int32","description":"Number of times the app was brought to focus"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","name","tool_type","date","time_slot","user_id","project_id","tracked","created_at","updated_at"],"description":"ToolUsage model"},"UrlActivityDetail":{"type":"object","properties":{"title":{"type":"string","description":"Page title"},"url":{"type":"string","description":"Visited URL"},"tracked":{"type":"integer","format":"int32","description":"Total time in seconds spent on this URL"}},"required":["title","url","tracked"]},"postV2OrganizationsOrganizationIdWebhooks":{"type":"object","properties":{"events":{"type":"array","description":"Array of event types to subscribe to. Use [\"*\"] for all events.","items":{"type":"string"}},"target_url":{"type":"string","description":"The URL that will receive webhook POST requests"}},"required":["events","target_url"],"description":"Create organization webhook"},"WorkBreakProject":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Work break project ID"},"billable":{"type":"boolean","description":"Whether time on this break is billable"},"description":{"type":"string","description":"Description of the break"},"name":{"type":"string","description":"Name of the work break"},"trackable":{"type":"boolean","description":"Whether the break is trackable"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"},"policy":{"$ref":"#/definitions/WorkBreakPolicy","description":"Associated break policy"}},"required":["id","billable","description","name","trackable","created_at","updated_at","policy"],"description":"WorkBreakProject model"},"WorkBreakPolicy":{"type":"object","properties":{"id":{"type":"integer","format":"int64","description":"Policy ID"},"policy_duration":{"type":"integer","format":"int32","description":"Configured duration of the break"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["id","policy_duration","created_at","updated_at"]},"putV2ProjectsProjectId":{"type":"object","properties":{"name":{"type":"string","description":"The new name of the project"},"description":{"type":"string","description":"The new description of the project"},"client_id":{"type":"integer","format":"int32","description":"The client ID"},"billable":{"type":"boolean","description":"Whether time logged against this project is by default billable"},"status":{"type":"string","description":"The project status.","enum":["active","archived"]},"budget":{"type":"object","description":"Budget configuration","properties":{"type":{"type":"string","description":"The method for controlling the budget","enum":["cost","hours"]},"rate":{"type":"string","description":"When budget type is cost, specifies which rate to use.","enum":["bill_rate","pay_rate"],"default":"bill_rate"},"cost":{"type":"number","format":"float","description":"When budget type is cost. defines the cost limit."},"hours":{"type":"number","format":"float","description":"When budget type is hours. defines the hours limit."},"start_date":{"type":"string","format":"date","description":"The start date for the budget."},"recurrence":{"type":"string","description":"If given, makes budget reset on a monthly basis","enum":["monthly"]},"include_non_billable":{"type":"boolean","description":"Whether the budget includes non billable time","default":true},"alerts":{"type":"object","properties":{"near_limit":{"type":"integer","format":"int32","description":"The percentage threshold for notifying when approaching the budget."}}}},"required":["type"]},"pay_rate":{"type":"number","format":"float","description":"Default pay rate (will be rounded to 2 decimal places). Set to null to remove the rate"},"bill_rate":{"type":"number","format":"float","description":"Default bill rate (will be rounded to 2 decimal places). Set to null to remove the rate"}},"description":"Update project"},"putV2ProjectsProjectIdUpdateMembers":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"role":{"type":"string","description":"Role for the user.","enum":["viewer","user","manager","remove"]},"pay_rate":{"type":"number","format":"float","description":"Default pay rate (will be rounded to 2 decimal places). Set to null to remove the rate"},"bill_rate":{"type":"number","format":"float","description":"Default bill rate (will be rounded to 2 decimal places). Set to null to remove the rate"}},"required":["user_id"]}}},"required":["members"],"description":"Update project members"},"putV2ProjectsProjectIdUpdateMetadata":{"type":"object","properties":{"metadata":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["metadata"],"description":"Update project metadata"},"ProjectMember":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"pay_rate":{"type":"string","description":"Pay rate"},"bill_rate":{"type":"string","description":"Bill rate"},"currency":{"type":"string","description":"Currency of pay and bill rates"},"membership_role":{"type":"string","description":"Membership role"},"membership_status":{"type":"string","description":"Membership status"},"created_at":{"type":"string","format":"date-time","description":"When the member was added to the organization/project"},"updated_at":{"type":"string","format":"date-time","description":"When the membership was last updated"},"removed_at":{"type":"string","format":"date-time","description":"When the member was removed"},"last_client_activity":{"type":"string","format":"date-time","description":"Last client activity of user"},"team_id":{"type":"integer","format":"int64","description":"Team ID"},"profile":{"$ref":"#/definitions/OrganizationMemberProfile"}},"required":["user_id","membership_role","membership_status","created_at","updated_at","last_client_activity"],"description":"ProjectMember model"},"postV2ProjectsProjectIdTasks":{"type":"object","properties":{"assignee_id":{"type":"integer","format":"int32","description":"Assignee Id"},"assignee_ids":{"type":"array","description":"Assignee Ids","items":{"type":"integer","format":"int32"}},"summary":{"type":"string","description":"To-do summary"},"pay_rate":{"type":"number","format":"float","description":"Pay rate. Set to null to remove"},"bill_rate":{"type":"number","format":"float","description":"Bill rate. Set to null to remove"},"metadata":{"type":"array","description":"Task metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["summary"],"description":"Create task"},"postV2ProjectsProjectIdWebhooks":{"type":"object","properties":{"events":{"type":"array","description":"Array of event types to subscribe to. Use [\"*\"] for all events.","items":{"type":"string"}},"target_url":{"type":"string","description":"The URL that will receive webhook POST requests"}},"required":["events","target_url"],"description":"Create project webhook"},"putV2TasksTaskId":{"type":"object","properties":{"assignee_id":{"type":"integer","format":"int32","description":"User Id"},"assignee_ids":{"type":"array","description":"User Ids","items":{"type":"integer","format":"int32"}},"lock_version":{"type":"integer","format":"int32","description":"Lock version"},"summary":{"type":"string","description":"To-do summary"},"status":{"type":"string","description":"The task status.","enum":["active","completed"]},"pay_rate":{"type":"number","format":"float","description":"Pay rate. Set to null to remove"},"bill_rate":{"type":"number","format":"float","description":"Bill rate. Set to null to remove"},"metadata":{"type":"array","description":"Task metadata. See the update_metadata endpoint","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["lock_version"],"description":"Update task"},"putV2TasksTaskIdUpdateMetadata":{"type":"object","properties":{"lock_version":{"type":"integer","format":"int32","description":"Lock version"},"metadata":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["lock_version","metadata"],"description":"Update task metadata"},"TeamMember":{"type":"object","properties":{"user_id":{"type":"integer","format":"int64","description":"User ID"},"role":{"type":"string","description":"Default Project role"},"lead":{"type":"boolean","description":"Is the member a team lead?"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["user_id","role","lead","created_at","updated_at"],"description":"TeamMember model"},"TeamProject":{"type":"object","properties":{"project_id":{"type":"integer","format":"int64","description":"Project ID"},"created_at":{"type":"string","format":"date-time","description":"When the record was created"},"updated_at":{"type":"string","format":"date-time","description":"When the record was last updated"}},"required":["project_id","created_at","updated_at"],"description":"TeamProject model"},"putV2TeamsTeamId":{"type":"object","properties":{"name":{"type":"string","description":"The new name of the team"},"lead_options":{"type":"object","properties":{"approve_timesheets":{"type":"boolean","description":"Team leads can approve timesheets for team members"},"notifications_for_timesheets_only_leads":{"type":"boolean","description":"Only team leeds receive timesheet notifications. Requires approve_timesheets enabled."},"approve_time_off":{"type":"boolean","description":"Team leads can approve time off for team members"},"approve_manual_time":{"type":"boolean","description":"Team leads can approve manual time added for team members"},"receive_manual_time_notification":{"type":"boolean","description":"Only team leads receive manual time notifications. Requires approve_manual_time enabled."},"notifications_for_time_off_only_leads":{"type":"boolean","description":"Only team leads receive time off notifications. Requires approve_time_off enabled."},"schedule_shifts":{"type":"boolean","description":"Team leads can schedule shifts for team members"},"notifications_for_schedules_only_leads":{"type":"boolean","description":"Only team leads receive notifications for shifts. Requires schedule_shifts enabled."},"notifications_for_limits":{"type":"boolean","description":"Team leads receive weekly and daily limit notifications for team members"},"notifications_for_limits_only_leads":{"type":"boolean","description":"Team lead only receive weekly and daily limit notifications. Requires notifications_for_limits enabled."},"notifications_work_breaks":{"type":"boolean","description":"Team leads receive work break notifications for team members"},"notifications_work_breaks_only_leads":{"type":"boolean","description":"Only team leads receive work break notifications. Requires notifications_work_breaks enabled."},"manage_team_projects":{"type":"boolean","description":"Team leads can create team projects"},"manage_team_members":{"type":"boolean","description":"Team leads can edit members and their roles"},"invite_members_to_organization":{"type":"boolean","description":"Team leads can invite new members to the organization"},"notifications_for_job_sites":{"type":"boolean","description":"Team leads receive job site notifications"},"notifications_for_job_sites_only_leads":{"type":"boolean","description":"Only team leads receive job site notifications. Requires notifications_for_job_sites enabled."},"others_screenshots":{"type":"boolean","description":"View screenshots/activities for other members"},"manage_billing":{"type":"boolean","description":"Manage financials"}}}},"description":"Update team"},"putV2TeamsTeamIdUpdateMembers":{"type":"object","properties":{"members":{"type":"array","items":{"type":"object","properties":{"user_id":{"type":"integer","format":"int32","description":"User ID"},"role":{"type":"string","description":"Role for the user.","enum":["viewer","user","manager","remove"]},"lead":{"type":"boolean","description":"Whether to set this user as a team lead"}},"required":["user_id"]}}},"required":["members"],"description":"Update team members"},"putV2TeamsTeamIdUpdateProjects":{"type":"object","properties":{"projects":{"type":"array","items":{"type":"object","properties":{"project_id":{"type":"integer","format":"int32","description":"List of project to add to the team"},"remove":{"type":"boolean","description":"If true then remove the project instead of adding it"}},"required":["project_id"]}}},"required":["projects"],"description":"Update team projects"},"putV2TeamsTeamIdUpdateMetadata":{"type":"object","properties":{"metadata":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","description":"The metadata key"},"value":{"type":"string","description":"The value for the metadata key"}},"required":["key","value"]}}},"required":["metadata"],"description":"Update team metadata"},"putV2TimeOffPoliciesTimeOffPolicyId":{"type":"object","properties":{"name":{"type":"string","description":"Policy name"},"accrual_policy":{"type":"object","description":"Accrual configuration (depends on accrual_type)","properties":{"hours_per_year":{"type":"number","format":"float"},"hours_worked":{"type":"number","format":"float"},"hours_per":{"type":"number","format":"float"},"maximum_to_accrue":{"type":"number","format":"float","description":"Max accrual hours/year (required except none and monthly)"},"hours_per_month":{"type":"number","format":"float"},"accrual_period":{"type":"string","enum":["month","year"]},"accrual_day":{"type":"string","enum":["first_day_of_month","monthly_anniversary","last_day_of_month"]},"prorate_on_start":{"type":"boolean"},"starting_balance":{"type":"number","format":"float"}}},"allow_negative_balances":{"type":"boolean"},"requires_approval":{"type":"boolean"},"balance_rolls_over_annually":{"type":"boolean"},"paid":{"type":"boolean"},"auto_add_new_users":{"type":"boolean"},"time_off_policy_user_ids":{"type":"array","description":"User IDs to set as members","items":{"type":"integer","format":"int32"}}},"description":"Update time off policy"},"putV2TimeOffRequestsTimeOffRequestIdStatus":{"type":"object","properties":{"status":{"type":"string","description":"New status","enum":["approved","denied"]},"response":{"type":"string","description":"Denial reason (required when denying)"},"remove_shifts":{"type":"boolean","description":"Remove overlapping shifts (used when approving)"}},"required":["status"],"description":"Update time off request status"},"putV2TimeOffRequestsTimeOffRequestId":{"type":"object","properties":{"time_off_policy_id":{"type":"integer","format":"int32","description":"Time off policy ID"},"starts_at":{"type":"string","format":"date-time","description":"Start time (ISO 8601)"},"stops_at":{"type":"string","format":"date-time","description":"Stop time (ISO 8601)"},"message":{"type":"string","description":"Reason for time off"},"all_day":{"type":"boolean","description":"Full day request","default":true},"exclude_weekends":{"type":"boolean","default":false},"exclude_holidays":{"type":"boolean","default":false},"require_no_shift_overlap":{"type":"boolean","description":"Reject if overlapping shifts exist","default":false},"remove_shifts":{"type":"boolean","description":"Remove overlapping attendance shifts. Only honored when the policy does not require approval (auto-approved requests). For policies that require approval, use the status endpoint to remove shifts upon approval."},"time_off_request_days":{"type":"array","description":"Day allocations","items":{"type":"object","properties":{"date":{"type":"string","format":"date"},"amount_used":{"type":"integer","format":"int32","description":"Seconds"}},"required":["date","amount_used"]}}},"required":["time_off_policy_id","starts_at","stops_at","message","time_off_request_days"],"description":"Update time off request"},"postV2UsersMeWebhooks":{"type":"object","properties":{"events":{"type":"array","description":"Array of event types to subscribe to. Use [\"*\"] for all events.","items":{"type":"string"}},"target_url":{"type":"string","description":"The URL that will receive webhook POST requests"}},"required":["events","target_url"],"description":"Create me webhook"},"postV2UsersUserIdTimeEntries":{"type":"object","properties":{"billable":{"type":"boolean","description":"Whether the time is billable or not. Defaults to the project setting."},"note":{"type":"string","description":"(it's REQUIRED by default) Can be modified to be optional in the Require Reason setting "},"project_id":{"type":"integer","format":"int32","description":"Project ID"},"start_time":{"type":"string","format":"date-time","description":"The start time for the activity in ISO8601 format"},"tracked":{"type":"integer","format":"int32","description":"The number of seconds tracked"},"task_id":{"type":"integer","format":"int32","description":"Task ID"}},"required":["project_id","start_time","tracked"],"description":"Create time entry"}}} \ No newline at end of file