Reference
Documents
Read tracking for a document you shared with somebody. There is one public endpoint and it is called by the viewer itself, not by an integrator: what it is for is knowing that the customer opened the quote three times and never called back.
Receives the public viewer's read-tracking heartbeats (T4-1). Time is
aggregated live onto the ViewSession and its pages — there is no raw
heartbeat table (D12). A hidden/idle tab reports visible=false and adds
nothing, which is what kills email-scanner "opens" (D12/R1).
Report reading time on a shared document
Records that a document is still open. Called by the public document viewer
every few seconds while somebody has
the document open, so the sender can see how long each page was actually
read. Send visible: false when the tab is hidden and the beat counts as
zero — that is what stops an email scanner from looking like a reader.
POST
/api/documents/heartbeat
public
Public on purpose: the reader has no account. What identifies the session
is session_token, handed out by the viewer itself when the link opens.
An unknown token gets a 404 and nothing is recorded.
Body parameters
-
session_tokenstring requiredThe token the viewer received when the shared link was opened.
Example:
01JAX2Q7K6MPQ4S5V8W9YZBCDE -
page_numberinteger requiredThe page being read, starting at 1.
Example:
3 -
visibleboolean requiredWhether the document is on screen right now. A hidden tab adds no time.
Example:
true
curl --request POST \
"https://klozzo.com/api/documents/heartbeat" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"session_token\": \"01JAX2Q7K6MPQ4S5V8W9YZBCDE\",
\"page_number\": 3,
\"visible\": true
}"
const url = new URL(
"https://klozzo.com/api/documents/heartbeat"
);
const headers = {
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"session_token": "01JAX2Q7K6MPQ4S5V8W9YZBCDE",
"page_number": 3,
"visible": true
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://klozzo.com/api/documents/heartbeat';
$response = $client->post(
$url,
[
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'json' => [
'session_token' => '01JAX2Q7K6MPQ4S5V8W9YZBCDE',
'page_number' => 3,
'visible' => true,
],
]
);
$body = $response->getBody();
print_r(json_decode((string) $body));
import requests
import json
url = 'https://klozzo.com/api/documents/heartbeat'
payload = {
"session_token": "01JAX2Q7K6MPQ4S5V8W9YZBCDE",
"page_number": 3,
"visible": true
}
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
response = requests.request('POST', url, headers=headers, json=payload)
response.json()
{
"ok": true,
"is_valid": true,
"is_completed": false
}
-
okboolean -
is_validboolean -
is_completedboolean
{
"ok": false
}
Every error shares the same shape — message and errors.
See Errors.
{
"message": "The session token field is required.",
"errors": {
"session_token": [
"The session token field is required."
]
}
}
Every error shares the same shape — message and errors.
See Errors.