curl --request GET \
--url https://sync.useparagon.com/api/permissions/{syncId}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://sync.useparagon.com/api/permissions/{syncId}/changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://sync.useparagon.com/api/permissions/{syncId}/changes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sync.useparagon.com/api/permissions/{syncId}/changes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sync.useparagon.com/api/permissions/{syncId}/changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://sync.useparagon.com/api/permissions/{syncId}/changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sync.useparagon.com/api/permissions/{syncId}/changes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"changes": [
{
"tupleKey": {
"user": "<string>",
"relation": "<string>",
"object": "<string>",
"condition": {
"name": "<string>",
"context": {}
}
},
"operation": "write",
"timestamp": "2023-11-07T05:31:56Z"
}
],
"continuationToken": "<string>"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "<string>",
"code": "PERMISSION_CHANGES_CURSOR_INVALID"
}Permission Changes
Read a chronological list of permission changes (writes and deletes) for a Sync
curl --request GET \
--url https://sync.useparagon.com/api/permissions/{syncId}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://sync.useparagon.com/api/permissions/{syncId}/changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://sync.useparagon.com/api/permissions/{syncId}/changes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sync.useparagon.com/api/permissions/{syncId}/changes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://sync.useparagon.com/api/permissions/{syncId}/changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://sync.useparagon.com/api/permissions/{syncId}/changes")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://sync.useparagon.com/api/permissions/{syncId}/changes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"changes": [
{
"tupleKey": {
"user": "<string>",
"relation": "<string>",
"object": "<string>",
"condition": {
"name": "<string>",
"context": {}
}
},
"operation": "write",
"timestamp": "2023-11-07T05:31:56Z"
}
],
"continuationToken": "<string>"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "<string>",
"code": "PERMISSION_CHANGES_CURSOR_INVALID"
}Paginating with continuation tokens
Every response includes acontinuationToken. Pass it on your next request to read the next page of changes.
When you reach the end of the feed, the response contains an empty changes array and the same continuationToken. Detect the end of the feed by the empty array, not by an absent token. Store the token and reuse it later to poll for new changes.
Keep these rules in mind when paginating:
- A continuation token takes precedence over
startTimewhen both are supplied. - A continuation token is issued against the
typefilter that produced it. Send the sametypeon every page of a paginated read, or omit it on every page. Changing it returns a 400 with the codePERMISSION_CHANGES_CURSOR_TYPE_MISMATCH. - If a token is no longer valid, the API returns a 400 with the code
PERMISSION_CHANGES_CURSOR_INVALID. Restart the read from the beginning or from astartTime.
Error codes
A rejected continuation token returns a 400 response with a stablecode field that you can branch on:
| Code | Meaning | How to recover |
|---|---|---|
PERMISSION_CHANGES_CURSOR_INVALID | The continuation token is not valid for this Sync. | Read again from the beginning, or from a startTime. |
PERMISSION_CHANGES_CURSOR_TYPE_MISMATCH | The type filter does not match the one the token was issued under. | Retry with the same type the token was issued under. |
Authorizations
Paragon User Token. Add to the Authorization header of your requests.
Path Parameters
UUID of the Sync to query, returned from the Enable Sync endpoint.
Query Parameters
Object type to filter changes by (e.g. file). A continuation token is issued against the type that produced it, so this value must stay identical for every page of a paginated read, including when it is omitted.
Only return changes that occurred at or after this time. Must be a full RFC 3339 timestamp with a time component (e.g. 2026-07-16T00:00:00Z); a date-only value is rejected with a 400. Ignored when continuationToken is supplied.
Number of changes to return per page, from 1 to 100. Defaults to 50.
1 <= x <= 100Token from a previous response, used to read the next page of changes. Takes precedence over startTime when both are supplied.
Response
A page of permission changes in chronological order
Permission changes in chronological order. An empty array indicates that you have reached the end of the feed.
Show child attributes
Show child attributes
Token to pass on the next request to read the next page. Repeated unchanged when there are no new changes, so detect the end of the feed by an empty changes array rather than by an absent token.
Was this page helpful?