Keys
- Store
tf_content_keys in a secret manager. Never commit or log them; log thekeyPrefixinstead. - Use one key per environment and per integration, so each can be limited, rate-limited, and rotated on its own.
- Give each key only the scopes it needs. Only
content:generatecan spend AI Credits, so a key without it cannot run builds, renders, or expansions. - Limit keys to the classrooms they work with through
classroomIds. - Set a
monthlyCreditLimiton keys that spend AI Credits, so a bug or a leaked key cannot spend the organization's whole balance. See Monthly credit limit. - Set
expiresAton keys issued to vendors or for a limited project. TutorFlow emails the organization's admins and sends anapi_key.expiringwebhook 7 days before; see Expiry warnings. - Narrow a key in place with
PATCHwhen it holds more than it needs, and rotate keys withisLegacyFullAccess: trueinto keys with explicit scopes. - Rotate on staff or vendor changes with a grace period, and rotate keys whose
issuerStatusisnot_admin. Rotate a key that may have leaked with no grace period; see If a key leaks. - Revoke keys you no longer use. The key list returns each key's
lastUsedAt.
A key can never create, change, rotate, or revoke keys; that needs an admin session. A key can manage webhooks only with webhooks:manage, which a classroom-limited key cannot hold. Give that scope only to the key that sets up your webhook receiver.
Learner data
Only learners:read and learners:write let a key reach individual learners: the learner routes, learner.* webhook subscriptions, and learner names in course stats. Both are opt-in, even for keys created before scopes existed. Grant them only to an integration that has to show or manage learners, and treat what it reads as personal data in your own system.
- Every call to a learner route, reads included, is recorded in the audit log.
- Learner responses carry
Cache-Control: no-store; do not cache them in shared caches or proxies on your side either. - Learner webhooks carry ids only, never names or email addresses.
- Keys with no scope list still receive learner names in course stats, for compatibility. Give such a key an explicit scope list if it does not need them. See Learner data.
Failed authentication
A client address that fails authentication 300 times in a minute is refused with 429 on every tf_content_ route for the rest of that minute, before any key is looked up. This stops key guessing from reaching the key store. Valid requests never count toward it. See Failed authentication limit.
Audit trail
Every request that changes something through a key is recorded with the key id, route, resource, status code, Idempotency-Key, and Request-Id, and kept for 90 days. So is every call to a learner route. Read it with GET /v1/content/audit-log; see Credit History and Audit Log. Key changes (create, change, rotate, revoke) and webhook changes (create, update, delete, secret rotation, test event, redelivery) made by an admin in Settings or with an admin session are recorded with the admin's user id. Resources a key creates are owned in TutorFlow by an organization admin, so the audit log is what ties a change to the integration that made it. resource.* webhooks carry the apiKeyId too.
Webhooks
- Verify
X-Content-Integration-Signature-V2over the raw body with a constant-time comparison, and accept the request if anyv1value matches. See Verify the signature. - Reject timestamps more than 5 minutes old, so a captured request cannot be replayed.
- Do not act on a request before its signature is verified.
- Skip event ids you have already processed.
- Store the signing secret like a key. TutorFlow stores it encrypted at rest and returns it only on create and rotate.
- Rotate the secret with a grace period for routine changes, and with none if it may have leaked. See Rotate the signing secret.
- Webhook URLs must be
httpsand resolve to a public address, checked on every delivery. Only307and308redirects are followed, up to 3 hops, each checked the same way.
Payloads
- Send only the content needed to create the resource. Do not include learner personal data in source JSON or
metadata. - Keep stable ids in your payloads so results can be matched without copying extra private fields.
- Confirm you own the content, or have the right to process it through TutorFlow, before you send it.
Uploads
- An upload URL lets anyone who holds it write one file for 15 minutes. Do not log it or pass it to a browser you do not control.
- Files uploaded as
thumbnail,scene-visual,overlay, orbgmare public: anyone with their address can load them. Upload private material, such as a lesson PDF or question audio, with its own purpose (lesson-pdf,test-audio); TutorFlow serves those only through signed links. - Resource fields refuse storage keys from other classrooms. See Asset Uploads.
Public links
A game or simulation whose visibility is PUBLIC opens at its play link for anyone with the link, without an account. Create integration-built games and simulations as PRIVATE, and switch them to PUBLIC only after a person has opened the build.
A rendered video's videoUrl is a signed link that works for 6 hours for anyone who has it. Do not log it or put it in client-side code; download the file and serve it under your own access control.
Logs and retention
Keep:
- Your source id, the TutorFlow resource or job id, the
Idempotency-Key, and theRequest-Idof each failed call. - The result manifest and the review status.
Do not keep:
- Full
tf_content_keys or webhook secrets, in logs or anywhere outside a secret manager. - Signed
videoUrllinks. - Copies of source payloads without a retention policy.
Before production
- The production key exists, has the narrowest scopes and classrooms, and is in the production secret manager.
- Key rotation can be done without a code change.
- The webhook receiver verifies signatures and timestamps and skips duplicates.
- Logs carry key prefixes and ids, never secrets.
- One small production-like request has succeeded end to end.