Guide: Handle a data request
A parent asks for everything you hold about their child. A family moves practices. A client turns eighteen. A clinic leaves your platform.
In each case somebody has to produce the record, and the part that lives in PlaySpace comes out in one call.
The request
const exp = await playspace.exports.create({
subject: { externalId: 'client_55130' },
include: [
'storybooks',
'worksheets',
'formResponses',
'sandtraySaves',
'dollhouseSaves',
'whiteboardSnapshots',
'generatedGames',
'models',
'sessionSummaries',
],
format: 'bundle',
requestedBy: { externalId: 'staff_8842' },
reason: 'caregiverRequest',
})
const { parts, expiresAt } = await exp.wait({ timeout: '30m' })
Name the families explicitly rather than using '*'. An export is a disclosure. Listing what you are disclosing makes the decision deliberate and leaves a better record of it.
reason is required and constrained: clientRequest, caregiverRequest, clinicalTransfer, migration, legal, internalReview. It is recorded against the export and appears in the audit history.
Who is allowed to ask
requestedBy must resolve to somebody with standing, and PlaySpace checks it.
| Requester | May export |
|---|---|
| Clinician | Their own clients |
| Practice administrator | Any client in their clinic |
| Caregiver | A dependent whose link carries canRequestExport |
A request without standing returns PermissionError with type export-not-permitted.
This is why canRequestExport exists on the caregiver link. Set it when you create the link, at the point where your own system knows who holds consent. Discovering it is unset at the moment a parent asks for records is an avoidable delay.
Clinical notes need three things
Notes are excluded by default and included only when all of the following are true:
- The organisation has the notes capability enabled.
notesappears explicitly ininclude.- The requester is a clinician or a practice administrator.
A caregiver-initiated export never contains notes, regardless of what was requested. That is enforced on our side rather than being left to your interface, because it is the kind of rule that is easy to get wrong once and hard to undo.
What arrives
export_client_55130_2026-09-14/
manifest.json every artifact, attribution, checksum
README.html a human-readable index
storybooks/
a-new-school/
a-new-school.pdf
pages/ 01.png … 08.png
storybook.json
worksheets/
feelings-animals/
blank.pdf
completed-2026-08-19.pdf
worksheet.json
forms/
caregiver-intake-2026-06-02.pdf
caregiver-intake-2026-06-02.json
sandtray/
2026-08-19-session/
view.png
scene.glb
placements.json
games/
river-crossing/ unzipped and playable offline
models/
terrier.glb
sessions/
2026-08-19-summary.pdf
README.html is the part that matters to a parent. They open the folder, double-click one file, and see their child's storybooks and drawings laid out in a page they can read. An export nobody can open is not portability.
manifest.json is the part that matters to a machine. Every artifact with its identifier, type, attribution triple, timestamps, file paths and checksum. Ingesting a PlaySpace export into another system means reading one file.
Every rendered format has a structured twin. No portable-document file carries information its .json omits.
Delivering it
const { parts, expiresAt } = await exp.wait()
for (const part of parts) {
const bytes = await fetch(part.url).then((r) => r.arrayBuffer())
await yourSecureDelivery.stage(bytes, { part: part.part })
}
Bundles are available for seven days, then deleted. Request another if you need it again — nothing is lost by letting one expire.
Do not email the link. The download link is credential-bearing: anyone holding it can retrieve the bundle for as long as it is live. Put the bundle behind your own authenticated delivery, the same way you would any other record disclosure.
One artifact instead of the whole record
Often the ask is smaller — a clinician wants the storybook, not the archive.
const { url } = await playspace.artifacts.download('sb_7Hn3xQ', { format: 'pdf' })
| Artifact | Formats |
|---|---|
| Storybook | pdf, epub, png per page, json |
| Worksheet, blank or completed | pdf, png per page, json |
| Form response | pdf, json, csv |
| Sandtray or dollhouse save | png, glb, json |
| Whiteboard snapshot | png, svg, json |
| Generated game | zip playable offline, json |
| Three-dimensional model | glb, png |
| Session summary | pdf, json |
| Clinical note | pdf, json |
A generated game exports as a game that still runs. Unzip it, open the index file, and it plays in a browser with no PlaySpace connection. Content a clinician made should not stop working because a subscription lapsed.
A whole-organisation export
For a platform migration or the end of a contract.
const exp = await playspace.exports.create({
scope: 'organisation',
include: ['*'],
format: 'bundle',
requestedBy: { externalId: 'staff_0001' },
reason: 'migration',
})
Structured by clinic, then clinician, then client, with library content that belongs to nobody in a top-level library/ directory.
A large organisation can take hours and run to hundreds of gigabytes, delivered as independently downloadable parts listed in the top-level manifest. Tell partner engineering before your first one so we can watch it.
What is never included
Another person's material. A sandtray two siblings built together exports to both bundles, because both made it. Nothing else crosses.
Uploaded source photographs. An image used to generate a three-dimensional model is discarded after generation. It cannot be exported because it does not exist.
Platform internals. Identifiers, audit rows, tokens and infrastructure metadata are not client records. Engagement telemetry appears in a session summary as durations and catalog titles, not as raw rows.
Anything archived. Archived material is unreadable to every reader on the platform, including an export.
The commitment
There is no export tier, no export fee, and no rate limit designed to make a migration tedious. There is no format here that needs our software to open.
If you are answering a procurement question about vendor lock-in, that is the answer, and it is testable in the sandbox before you sign anything.