curl --request GET \
--url 'https://api.simkl.com/users/{user_id}/stats?client_id=' \
--header 'User-Agent: <user-agent>'import requests
url = "https://api.simkl.com/users/{user_id}/stats?client_id="
headers = {"User-Agent": "<user-agent>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch('https://api.simkl.com/users/{user_id}/stats?client_id=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch('https://api.simkl.com/users/{user_id}/stats?client_id=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.simkl.com/users/{user_id}/stats?client_id=")
.get()
.addHeader("User-Agent", "<user-agent>")
.build()
val response = client.newCall(request).execute()import Foundation
let url = URL(string: "https://api.simkl.com/users/{user_id}/stats")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [
URLQueryItem(name: "client_id", value: ""),
]
var queryAllowed = CharacterSet.urlQueryAllowed
queryAllowed.remove(charactersIn: "&=+#")
let encodedQuery = queryItems.map { "\($0.name.addingPercentEncoding(withAllowedCharacters: queryAllowed)!)=\(($0.value ?? "").addingPercentEncoding(withAllowedCharacters: queryAllowed)!)" }.joined(separator: "&")
components.percentEncodedQuery = components.percentEncodedQuery.map { $0 + "&" + encodedQuery } ?? encodedQuery
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 10
request.allHTTPHeaderFields = ["User-Agent": "<user-agent>"]
let (data, _) = try await URLSession.shared.data(for: request)
print(String(decoding: data, as: UTF8.self))HttpResponse<String> response = Unirest.get("https://api.simkl.com/users/{user_id}/stats?client_id=")
.header("User-Agent", "<user-agent>")
.asString();const url = 'https://api.simkl.com/users/{user_id}/stats?client_id=';
const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.simkl.com/users/{user_id}/stats?client_id="
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("User-Agent", "<user-agent>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}using RestSharp;
var options = new RestClientOptions("https://api.simkl.com/users/{user_id}/stats?client_id=");
var client = new RestClient(options);
var request = new RestRequest("");
request.AddHeader("User-Agent", "<user-agent>");
var response = await client.GetAsync(request);
Console.WriteLine("{0}", response.Content);
using RestSharp;
var options = new RestClientOptions("https://api.simkl.com/users/{user_id}/stats?client_id=");
var client = new RestClient(options);
var request = new RestRequest("");
request.AddHeader("User-Agent", "<user-agent>");
var response = await client.GetAsync(request);
Console.WriteLine("{0}", response.Content);
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.simkl.com/users/{user_id}/stats?client_id=",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"User-Agent: <user-agent>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}require 'uri'
require 'net/http'
url = URI("https://api.simkl.com/users/{user_id}/stats?client_id=")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["User-Agent"] = '<user-agent>'
response = http.request(request)
puts response.read_body$headers=@{}
$headers.Add("User-Agent", "<user-agent>")
$response = Invoke-WebRequest -Uri 'https://api.simkl.com/users/{user_id}/stats?client_id=' -Method GET -Headers $headersCURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, stdout);
curl_easy_setopt(hnd, CURLOPT_URL, "https://api.simkl.com/users/{user_id}/stats?client_id=");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "User-Agent: <user-agent>");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
CURLcode ret = curl_easy_perform(hnd);CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, stdout);
curl_easy_setopt(hnd, CURLOPT_URL, "https://api.simkl.com/users/{user_id}/stats?client_id=");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "User-Agent: <user-agent>");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
CURLcode ret = curl_easy_perform(hnd);{
"total_mins": 908554,
"movies": {
"total_mins": 171969,
"plantowatch": {
"mins": 18938,
"count": 173
},
"dropped": {
"mins": 8296,
"count": 73
},
"completed": {
"mins": 171852,
"count": 1558
}
},
"tv": {
"total_mins": 242560,
"watching": {
"watched_episodes_count": 2612,
"count": 41,
"left_to_watch_episodes": 10276,
"left_to_watch_mins": 411040,
"total_episodes_count": 12888
},
"hold": {
"watched_episodes_count": 1157,
"count": 41,
"left_to_watch_episodes": 828,
"left_to_watch_mins": 33120,
"total_episodes_count": 1985
},
"plantowatch": {
"watched_episodes_count": 243,
"count": 118,
"left_to_watch_episodes": 12961,
"left_to_watch_mins": 518440,
"total_episodes_count": 13204
},
"dropped": {
"watched_episodes_count": 412,
"count": 28
},
"completed": {
"watched_episodes_count": 2111,
"count": 55
}
},
"anime": {
"total_mins": 494025,
"watching": {
"watched_episodes_count": 1747,
"count": 21,
"left_to_watch_episodes": 410,
"left_to_watch_mins": 10250,
"total_episodes_count": 2157
},
"hold": {
"watched_episodes_count": 97,
"count": 23,
"left_to_watch_episodes": 462,
"left_to_watch_mins": 11550,
"total_episodes_count": 559
},
"plantowatch": {
"watched_episodes_count": 2324,
"count": 719,
"left_to_watch_episodes": 7829,
"left_to_watch_mins": 195725,
"total_episodes_count": 10153
},
"dropped": {
"watched_episodes_count": 412,
"count": 28
},
"completed": {
"watched_episodes_count": 17255,
"count": 1291
}
},
"watched_last_week": {
"total_mins": 130,
"movies_mins": 130,
"tv_mins": 0,
"anime_mins": 0
}
}{
"error": "private_profile",
"code": 403
}{
"error": "user_id_failed",
"code": 404
}{
"error": "client_id_failed",
"code": 412,
"message": "Your client_id is wrong. Try another one"
}{
"error": "rate_limit",
"code": 429
}{
"error": "internal",
"code": 500
}Get a user's watch statistics
The most expensive call in the Simkl API. Only fire it on an explicit user action.
Stats are computed live on every request — there is no edge cache and no precomputed result cache. The server walks the user’s entire watch history across all three catalogs (movies, TV, anime), looks up the runtime of every completed episode and movie, and aggregates everything from scratch. Response time scales with the size of the user’s library.
OK to call: when the user opens a “My stats” / “Year in review” / profile screen, or taps a refresh button on a stats widget.
Do not call: on app launch, on resume from background, in any polling loop, speculatively to “warm” data, or for every user in a list (e.g. a friends leaderboard — batch via lazy loading). Apps that hammer this endpoint risk rate-limit throttling on the client_id.
Returns aggregate stats for the given user — total movies / shows / anime watched, total time spent, episode counts and last-week activity.
The user_id must be a positive integer — the numeric Simkl id of the target account. To fetch stats for the authenticated user, call GET /users/settings once at app start and cache account.id, then pass that value here.
Public profiles can be fetched without a bearer token (clientId-only). Private profiles require either a bearer token belonging to the target user, or a connection the target user has granted the requester (otherwise 403 private_profile).
Response shape
{
"total_mins": 78230,
"movies": {
"total_mins": 18000,
"plantowatch": { "mins": 0, "count": 12 },
"completed": { "mins": 18000, "count": 200 },
"dropped": { "mins": 0, "count": 1 }
},
"tv": {
"total_mins": 35000,
"watching": { "watched_episodes_count": 23, "count": 4, "left_to_watch_episodes": 12, "left_to_watch_mins": 600, "total_episodes_count": 35 }
},
"anime": {... },
"watched_last_week": { "total_mins": 320, "movies_mins": 60, "tv_mins": 200, "anime_mins": 60 }
}
Errors
| Code | When |
|---|---|
404 user_id_failed | user_id is 0 or any non-positive integer. There is no shortcut for the authenticated user — always pass a real numeric id. |
403 private_profile | The target user’s profile is private and the requester does not have access. |
curl --request GET \
--url 'https://api.simkl.com/users/{user_id}/stats?client_id=' \
--header 'User-Agent: <user-agent>'import requests
url = "https://api.simkl.com/users/{user_id}/stats?client_id="
headers = {"User-Agent": "<user-agent>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch('https://api.simkl.com/users/{user_id}/stats?client_id=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch('https://api.simkl.com/users/{user_id}/stats?client_id=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.simkl.com/users/{user_id}/stats?client_id=")
.get()
.addHeader("User-Agent", "<user-agent>")
.build()
val response = client.newCall(request).execute()import Foundation
let url = URL(string: "https://api.simkl.com/users/{user_id}/stats")!
var components = URLComponents(url: url, resolvingAgainstBaseURL: true)!
let queryItems: [URLQueryItem] = [
URLQueryItem(name: "client_id", value: ""),
]
var queryAllowed = CharacterSet.urlQueryAllowed
queryAllowed.remove(charactersIn: "&=+#")
let encodedQuery = queryItems.map { "\($0.name.addingPercentEncoding(withAllowedCharacters: queryAllowed)!)=\(($0.value ?? "").addingPercentEncoding(withAllowedCharacters: queryAllowed)!)" }.joined(separator: "&")
components.percentEncodedQuery = components.percentEncodedQuery.map { $0 + "&" + encodedQuery } ?? encodedQuery
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.timeoutInterval = 10
request.allHTTPHeaderFields = ["User-Agent": "<user-agent>"]
let (data, _) = try await URLSession.shared.data(for: request)
print(String(decoding: data, as: UTF8.self))HttpResponse<String> response = Unirest.get("https://api.simkl.com/users/{user_id}/stats?client_id=")
.header("User-Agent", "<user-agent>")
.asString();const url = 'https://api.simkl.com/users/{user_id}/stats?client_id=';
const options = {method: 'GET', headers: {'User-Agent': '<user-agent>'}};
fetch(url, options)
.then(res => res.json())
.then(json => console.log(json))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.simkl.com/users/{user_id}/stats?client_id="
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("User-Agent", "<user-agent>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}using RestSharp;
var options = new RestClientOptions("https://api.simkl.com/users/{user_id}/stats?client_id=");
var client = new RestClient(options);
var request = new RestRequest("");
request.AddHeader("User-Agent", "<user-agent>");
var response = await client.GetAsync(request);
Console.WriteLine("{0}", response.Content);
using RestSharp;
var options = new RestClientOptions("https://api.simkl.com/users/{user_id}/stats?client_id=");
var client = new RestClient(options);
var request = new RestRequest("");
request.AddHeader("User-Agent", "<user-agent>");
var response = await client.GetAsync(request);
Console.WriteLine("{0}", response.Content);
<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.simkl.com/users/{user_id}/stats?client_id=",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"User-Agent: <user-agent>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}require 'uri'
require 'net/http'
url = URI("https://api.simkl.com/users/{user_id}/stats?client_id=")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["User-Agent"] = '<user-agent>'
response = http.request(request)
puts response.read_body$headers=@{}
$headers.Add("User-Agent", "<user-agent>")
$response = Invoke-WebRequest -Uri 'https://api.simkl.com/users/{user_id}/stats?client_id=' -Method GET -Headers $headersCURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, stdout);
curl_easy_setopt(hnd, CURLOPT_URL, "https://api.simkl.com/users/{user_id}/stats?client_id=");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "User-Agent: <user-agent>");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
CURLcode ret = curl_easy_perform(hnd);CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, stdout);
curl_easy_setopt(hnd, CURLOPT_URL, "https://api.simkl.com/users/{user_id}/stats?client_id=");
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "User-Agent: <user-agent>");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);
CURLcode ret = curl_easy_perform(hnd);{
"total_mins": 908554,
"movies": {
"total_mins": 171969,
"plantowatch": {
"mins": 18938,
"count": 173
},
"dropped": {
"mins": 8296,
"count": 73
},
"completed": {
"mins": 171852,
"count": 1558
}
},
"tv": {
"total_mins": 242560,
"watching": {
"watched_episodes_count": 2612,
"count": 41,
"left_to_watch_episodes": 10276,
"left_to_watch_mins": 411040,
"total_episodes_count": 12888
},
"hold": {
"watched_episodes_count": 1157,
"count": 41,
"left_to_watch_episodes": 828,
"left_to_watch_mins": 33120,
"total_episodes_count": 1985
},
"plantowatch": {
"watched_episodes_count": 243,
"count": 118,
"left_to_watch_episodes": 12961,
"left_to_watch_mins": 518440,
"total_episodes_count": 13204
},
"dropped": {
"watched_episodes_count": 412,
"count": 28
},
"completed": {
"watched_episodes_count": 2111,
"count": 55
}
},
"anime": {
"total_mins": 494025,
"watching": {
"watched_episodes_count": 1747,
"count": 21,
"left_to_watch_episodes": 410,
"left_to_watch_mins": 10250,
"total_episodes_count": 2157
},
"hold": {
"watched_episodes_count": 97,
"count": 23,
"left_to_watch_episodes": 462,
"left_to_watch_mins": 11550,
"total_episodes_count": 559
},
"plantowatch": {
"watched_episodes_count": 2324,
"count": 719,
"left_to_watch_episodes": 7829,
"left_to_watch_mins": 195725,
"total_episodes_count": 10153
},
"dropped": {
"watched_episodes_count": 412,
"count": 28
},
"completed": {
"watched_episodes_count": 17255,
"count": 1291
}
},
"watched_last_week": {
"total_mins": 130,
"movies_mins": 130,
"tv_mins": 0,
"anime_mins": 0
}
}{
"error": "private_profile",
"code": 403
}{
"error": "user_id_failed",
"code": 404
}{
"error": "client_id_failed",
"code": 412,
"message": "Your client_id is wrong. Try another one"
}{
"error": "rate_limit",
"code": 429
}{
"error": "internal",
"code": 500
}Authorizations
Preferred form: your client_id as a URL query parameter on every request. Self-describing in logs and curl commands. See Headers and required parameters.
Headers
Descriptive identifier for your app, ideally name/version. Examples: PlexMediaServer/1.43.1.10540, kodi-simkl/0.9.2, MyApp/2.4.1 (https://myapp.com).
Path Parameters
Target user's numeric Simkl id (positive integer). For the authenticated user, first call GET /users/settings and pass back account.id. Passing 0 returns 404 user_id_failed.
x >= 1Query Parameters
Your client_id from your Simkl developer settings. Required on every request.
Short, lowercase identifier for your app (e.g. plex-scrobbler, kodi-bridge). Helps Simkl identify which apps are using the API.
Your app's current version (e.g. 1.0, 2.4.1). Helps Simkl debug issues you report.
Was this page helpful?