Subscriptions
GET Get current subscription
https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current
Get current subscription.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current \
-H "Content-Type: application/json"
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current", {
method: "GET",
headers: {
"Content-Type": "application/json"
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current', headers={'content-type': 'application/json'})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/current".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
Response
200
| Name | Type | Description |
|---|---|---|
| defers_commitment_reductions | boolean | DefersCommitmentReductions is omitted when a plan-tier downgrade or a switch to a shorter billing period on this subscription applies immediately. When true, such a reduction may take effect at the end of the current period instead: the feature is on and the subscription is an active, paid annual commitment that is not cancelling. The plan change can still apply immediately, or be refused, for reasons specific to the request (seats added in the same change, a module add-on on the same product, another product's scheduled change), so deferred on the plan change response is the authority on what happened. |
| focus_trial_ended_at | string | null |
| granted | boolean | Granted reports an entitlement the organization holds without paying for it: the Stripe-backed fields are empty because no Stripe subscription backs the grant. |
| organization_id | integer | - |
| price | object | - |
| product | object | - |
| subscription | object | - |
| subscription_item | object | - |
| track_subscription | object | - |
price
| Name | Type | Description |
|---|---|---|
| billing_period_in_months | integer | - |
| cost_in_cents | integer | - |
| currency | string | - |
| tag | string | - |
| track_pricing_plan_id | integer | - |
product
| Name | Type | Description |
|---|---|---|
| handle | string | - |
| name | string | - |
| site | string | - |
| toggl_product_id | string | - |
| track_enterprise | boolean | - |
| track_plan_id | integer | - |
subscription
| Name | Type | Description |
|---|---|---|
| customer | object | - |
| grant_source_subscription_id | string | GrantSourceSubscriptionID is the paid Stripe subscription whose plan grants it. |
| granted | boolean | Granted reports a product the organization holds through another product's paid plan; no Stripe subscription backs it. |
| id | string | - |
| payment_failed | boolean | - |
| state | string | - |
| subscription_item | object | - |
| url | string | - |
customer
| Name | Type | Description |
|---|---|---|
| id | string | - |
| url | string | - |
subscription_item
| Name | Type | Description |
|---|---|---|
| discount | object | - |
| quantity | integer | Quantity is the live paid quantity (number of paid licenses). |
| scheduled_change | object | ScheduledChange describes a plan or cadence change deferred to the end of the current period; nil when no plan change is pending. |
| scheduled_quantity | integer | ScheduledQuantity is the scheduled next-cycle quantity when a change is pending in Stripe (typically a decrease); nil when there is no pending change — no schedule, or the scheduled quantity equals the live quantity. |
| subscription_item_id | string | ID is the Stripe subscription item ID. Empty for trial-only synthetic responses where no Stripe subscription item exists yet. |
| updated_at | string | - |
discount
| Name | Type | Description |
|---|---|---|
| amount_off | integer | - |
| coupon_id | string | - |
| currency | string | - |
| duration | string | - |
| duration_in_months | integer | DurationInMonths is the coupon's month count for a repeating discount; nil otherwise. |
| ends_at | string | EndsAt is when the discount stops applying (RFC3339); nil when it has no end. |
| name | string | - |
| percent_off | number | - |
| promotion_code_id | string | - |
scheduled_change
| Name | Type | Description |
|---|---|---|
| billing_period_in_months | integer | BillingPeriodInMonths is the new price's billing period (1 = monthly, 12 = annual). |
| cost_in_cents | integer | CostInCents is the new price's per-seat cost. |
| effective_at | string | EffectiveAt is the date the change applies (the current period end), YYYY-MM-DD. |
| pricing_plan_tag | string | PricingPlanTag is the tag of the price the subscription moves to at period end. |
subscription_item
| Name | Type | Description |
|---|---|---|
| discount | object | - |
| quantity | integer | Quantity is the live paid quantity (number of paid licenses). |
| scheduled_change | object | ScheduledChange describes a plan or cadence change deferred to the end of the current period; nil when no plan change is pending. |
| scheduled_quantity | integer | ScheduledQuantity is the scheduled next-cycle quantity when a change is pending in Stripe (typically a decrease); nil when there is no pending change — no schedule, or the scheduled quantity equals the live quantity. |
| subscription_item_id | string | ID is the Stripe subscription item ID. Empty for trial-only synthetic responses where no Stripe subscription item exists yet. |
| updated_at | string | - |
discount
| Name | Type | Description |
|---|---|---|
| amount_off | integer | - |
| coupon_id | string | - |
| currency | string | - |
| duration | string | - |
| duration_in_months | integer | DurationInMonths is the coupon's month count for a repeating discount; nil otherwise. |
| ends_at | string | EndsAt is when the discount stops applying (RFC3339); nil when it has no end. |
| name | string | - |
| percent_off | number | - |
| promotion_code_id | string | - |
scheduled_change
| Name | Type | Description |
|---|---|---|
| billing_period_in_months | integer | BillingPeriodInMonths is the new price's billing period (1 = monthly, 12 = annual). |
| cost_in_cents | integer | CostInCents is the new price's per-seat cost. |
| effective_at | string | EffectiveAt is the date the change applies (the current period end), YYYY-MM-DD. |
| pricing_plan_tag | string | PricingPlanTag is the tag of the price the subscription moves to at period end. |
track_subscription
| Name | Type | Description |
|---|---|---|
| cancel_date | string | - |
| current_period_ends_at | string | - |
| current_period_starts_at | string | - |
| end_date | string | - |
| pause_eligible | boolean | PauseEligible reports whether a pause request would be accepted right now. It is a strong hint computed from stored data, not a promise. The pause endpoint stays authoritative: the one rule it applies that is missing here is pending invoice items, which costs a Stripe call to see and has no place on a cached read path. PauseIneligibleReason names the rule that failed, as a stable code for clients to map to their own copy, and is nil while PauseEligible is true. |
| pause_ineligible_reason | string | Valid values: already_used, already_paused, not_monthly, trialing, not_active, cancel_scheduled, not_card, discount_scheduled, unsupported_pause. |
| pause_starts_at | string | - |
| paused | boolean | Paused is true while the organization is in the unpaid months of a self-serve pause, which is when products make it read-only; the subscription itself stays "active". PauseStartsAt is the first skipped renewal, where those months start, and ResumesAt the renewal billing restarts at. Both are set from the moment the pause applies until billing restarts or the customer resumes, so clients can show a pause that has not started yet. |
| payment_failed | boolean | - |
| resumable | boolean | Resumable is true while a self-serve pause is open, which is when a resume request has something to act on. It is false for a collection pause applied from the Stripe Dashboard or by support: those are not self-serve pauses and the resume endpoint does not lift them. |
| resumes_at | string | - |
| start_date | string | - |
| subscription_created_at | string | - |
| subscription_updated_at | string | - |
| trial | object | - |
trial
| Name | Type | Description |
|---|---|---|
| available | boolean | - |
| end_date | string | - |
| start_date | string | - |
| trial_plan_id | integer | - |
204
No subscription and no plan
400
Invalid request
403
Insufficient permissions
500
Internal Server Error
GET Get current trial
https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current
Get the organization's current active Focus plan trial. Add-on module trials (Capacity, Time Off) are excluded: they run alongside the organization's own plan rather than standing in for it.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current \
-H "Content-Type: application/json"
req, err := http.NewRequest(http.MethodGet,
"https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current")
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current')
req = Net::HTTP::Get.new(uri)
req['Content-Type'] = "application/json"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current", {
method: "GET",
headers: {
"Content-Type": "application/json"
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.get('https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current', headers={'content-type': 'application/json'})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let json = client.request(Method::GET, "https://focus.toggl.com/api/organizations/{organization_id}/subscriptions/trials/current".to_string())
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
Response
200
Current active trial
| Name | Type | Description |
|---|---|---|
| auto_upgrade | boolean | - |
| auto_upgrade_notified_at | string | AutoUpgradeNotifiedAt is when the trial-ending notice was last sent, if ever. |
| id | integer | - |
| organization_id | integer | - |
| plan_id | integer | - |
| plan_name | string | - |
| product_handle | string | - |
| toggl_product_name | string | - |
| trial_cancels_at | string | - |
| trial_created_at | string | - |
| trial_deleted_at | string | - |
| trial_ended_at | string | - |
| trial_started_at | string | - |
| trial_type | string | - |
204
No active trial found
400
Invalid request
404
Subscription not found
500
Internal server error
POST Upserts a trial
https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial
Creates or updates a trial for the organization based on workspace data and Plan import history.
- cURL
- Go
- Ruby
- JavaScript
- Python
- Rust
curl -X POST https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial \
-H "Content-Type: application/json" \
-d '\{"plan_workspace_id":"integer"\}'
bytes, err := json.Marshal('\{"plan_workspace_id":"integer"\}')
if err != nil {
print(err)
}
req, err := http.NewRequest(http.MethodPost,
"https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial", bytes.NewBuffer(bytes))
if err != nil {
print(err)
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
print(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
print(err)
}
fmt.Print(string(body))
require 'net/http'
require 'uri'
require 'json'
uri = URI('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial')
req = Net::HTTP::Post.new(uri)
req['Content-Type'] = "application/json"
req.body = \{"plan_workspace_id":"integer"\}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(req)
end
puts JSON.parse(res.body)
fetch("https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial", {
method: "POST",
body: \{"plan_workspace_id":"integer"\},
headers: {
"Content-Type": "application/json"
},
})
.then((resp) => resp.json())
.then((json) => {
console.log(json);
})
.catch(err => console.error(err));
import requests
from base64 import b64encode
data = requests.post('https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial', json=\{"plan_workspace_id":"integer"\}, headers={'content-type': 'application/json'})
print(data.json())
extern crate tokio;
extern crate serde_json;
use reqwest::{Client};
use reqwest::header::{CONTENT_TYPE};
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let json = client.request(Method::POST, "https://focus.toggl.com/api/organizations/{organization_id}/workspaces/{workspace_id}/subscriptions/upsert-forced-trial".to_string())
.json(&serde_json::json!(\{"plan_workspace_id":"integer"\}))
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json()
.await?;
println!("{:#?}", json);
Ok(())
}
Parameters
Path
| name | type | required | description |
|---|---|---|---|
| organization_id | integer | true | organization ID |
| workspace_id | integer | true | workspace ID |
Body
| Name | Type | Description |
|---|---|---|
| plan_workspace_id | integer | - |
Response
200
Trial was created or updated
| Name | Type | Description |
|---|---|---|
| auto_upgrade | boolean | - |
| auto_upgrade_notified_at | string | AutoUpgradeNotifiedAt is when the trial-ending notice was last sent, if ever. |
| id | integer | - |
| organization_id | integer | - |
| plan_id | integer | - |
| plan_name | string | - |
| product_handle | string | - |
| toggl_product_name | string | - |
| trial_cancels_at | string | - |
| trial_created_at | string | - |
| trial_deleted_at | string | - |
| trial_ended_at | string | - |
| trial_started_at | string | - |
| trial_type | string | - |
204
No trial created or updated (existing unchanged trial, paid subscription, or no eligible trial)
400
Invalid request
409
Forced trial already exists or org has active subscription
500
Internal server error