Reference: sync manifest
GET /sync
Scope read. Every document and category the key can see, with only the fields needed to compare. Use it to find what has gone: deleted, archived out of the released view, or out of the acting person's reach.
false if there are more than 50,000 documents and the list was cut off. Then do not delete anything based on it.Request
curl https://ddm.example.com/api/ext/v1/sync \
-H "Authorization: Bearer $DDM_API_KEY"
Response · 200 OK
{
"generatedAt": "2026-10-27T20:01:47.990Z",
"complete": true,
"documents": [
{
"id": "dc8594e0-0ead-4049-aaee-2ef899bcf76f",
"externalRef": "PIM-4711",
"sourceId": "5d02b7aa-5175-42e9-9277-aa19063833f2",
"status": "draft",
"currentVersionId": "1730e7bb-2f98-4761-aa9b-964c521351b3",
"checksum": "0bcb7c3b4886f699a985563d7bc9476bf75b4af35a57ff959f46c37e386cf54f",
"categoryIds": [
"078a554f-19a2-4285-b854-8546de01f815"
],
"updatedAt": "2026-10-27T20:01:47.284Z"
}
],
"categories": [
{
"id": "7a7afae0-f77b-478b-975d-d29eace15d51",
"parentId": null,
"externalRef": "CAT-10",
"updatedAt": "2026-10-27T20:01:46.086Z"
},
{
"id": "078a554f-19a2-4285-b854-8546de01f815",
"parentId": "7a7afae0-f77b-478b-975d-d29eace15d51",
"externalRef": "CAT-11",
"updatedAt": "2026-10-27T20:01:46.138Z"
},
{
"id": "45dc485e-d982-466a-8ab6-e8ac328b800f",
"parentId": "7a7afae0-f77b-478b-975d-d29eace15d51",
"externalRef": null,
"updatedAt": "2026-10-27T20:01:46.200Z"
}
]
}
Example: finding deleted documents
Keep the manifest of each run. On the next run, fetch a new one and compare: every id that was in the old one and is missing from the new one is gone for this key. The document may have been deleted, archived out of the released view, or moved where the key's person cannot see it.
# The manifest of the last run is in manifest-previous.json.
curl -s https://ddm.example.com/api/ext/v1/sync \
-H "Authorization: Bearer $DDM_API_KEY" > manifest.json
# Cut off above 50,000 documents? Then compare nothing.
if [ "$(jq .complete manifest.json)" != "true" ]; then
echo "Manifest incomplete – not deleting anything" >&2; exit 1
fi
# The ids that were there last time and are not any more.
jq -r '.documents[].id' manifest-previous.json | sort > previous.txt
jq -r '.documents[].id' manifest.json | sort > current.txt
comm -23 previous.txt current.txt > gone.txt
while read -r id; do
echo "gone: $id" # remove it from your system here
done < gone.txt
mv manifest.json manifest-previous.json
import { readFile, writeFile } from 'node:fs/promises'
const response = await fetch('https://ddm.example.com/api/ext/v1/sync', {
headers: { Authorization: `Bearer ${process.env.DDM_API_KEY}` }
})
const manifest = await response.json()
// Cut off above 50,000 documents? Then compare nothing.
if (!manifest.complete) throw new Error('Manifest incomplete – not deleting anything')
const previous = JSON.parse(await readFile('manifest-previous.json', 'utf8').catch(() => '{"documents":[]}'))
const present = new Set(manifest.documents.map(document => document.id))
const gone = previous.documents.filter(document => !present.has(document.id))
for (const document of gone) {
// externalRef is set for documents that came from a push – often the id your system knows.
console.log('gone:', document.id, document.externalRef ?? '')
// remove it from your system here
}
await writeFile('manifest-previous.json', JSON.stringify(manifest))
import json, os, requests
manifest = requests.get(
"https://ddm.example.com/api/ext/v1/sync",
headers={"Authorization": f"Bearer {os.environ['DDM_API_KEY']}"},
).json()
# Cut off above 50,000 documents? Then compare nothing.
if not manifest["complete"]:
raise SystemExit("Manifest incomplete – not deleting anything")
try:
with open("manifest-previous.json") as file:
previous = json.load(file)
except FileNotFoundError:
previous = {"documents": []}
present = {document["id"] for document in manifest["documents"]}
gone = [document for document in previous["documents"] if document["id"] not in present]
for document in gone:
print("gone:", document["id"], document.get("externalRef") or "")
# remove it from your system here
with open("manifest-previous.json", "w") as file:
json.dump(manifest, file)
$headers = @{ Authorization = "Bearer $env:DDM_API_KEY" }
$manifest = Invoke-RestMethod -Uri "https://ddm.example.com/api/ext/v1/sync" -Headers $headers
# Cut off above 50,000 documents? Then compare nothing.
if (-not $manifest.complete) { throw "Manifest incomplete – not deleting anything" }
$previous = if (Test-Path manifest-previous.json) { Get-Content manifest-previous.json -Raw | ConvertFrom-Json } else { @{ documents = @() } }
$present = [System.Collections.Generic.HashSet[string]]::new([string[]]$manifest.documents.id)
foreach ($document in $previous.documents) {
if (-not $present.Contains($document.id)) {
Write-Output "gone: $($document.id) $($document.externalRef)"
# remove it from your system here
}
}
$manifest | ConvertTo-Json -Depth 5 | Set-Content manifest-previous.json
What "gone" means is the same in all three cases: remove the document from your system, or hide it there. If you need to know why it is gone, ask GET /documents/{id} with a key that sees everything. 404 means it was deleted; otherwise its status and categoryIds tell you what changed.
complete is true. And treat a manifest that is suddenly empty or much smaller with suspicion – a revoked permission or a changed acting person makes everything "disappear" at once. A sensible guard: stop and alert someone if more than, say, 10 % of documents would be deleted in one run.Change feed or manifest?
| Question | Use |
|---|---|
| What is new or changed since my last run? | GET /documents?updatedSince=… – small, fast, paged |
| What is gone? | GET /sync – complete, but larger |
| Has anything been re-filed or re-tagged in the CMS? | GET /sync for the filing, GET /documents/{id} for the tags |
A good rhythm: the change feed every few minutes, and the manifest once an hour or once a night.