Resources
Content API Security

Content API Security

Handle Content API keys, webhook secrets, payloads, public links, and logs safely in production.

On this page

Keys

  • Store tf_content_ keys in a secret manager. Never commit or log them; log the keyPrefix instead.
  • 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:generate can 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 monthlyCreditLimit on keys that spend AI Credits, so a bug or a leaked key cannot spend the organization's whole balance. See Monthly credit limit.
  • Set expiresAt on keys issued to vendors or for a limited project. TutorFlow emails the organization's admins and sends an api_key.expiring webhook 7 days before; see Expiry warnings.
  • Narrow a key in place with PATCH when it holds more than it needs, and rotate keys with isLegacyFullAccess: true into keys with explicit scopes.
  • Rotate on staff or vendor changes with a grace period, and rotate keys whose issuerStatus is not_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-V2 over the raw body with a constant-time comparison, and accept the request if any v1 value 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 https and resolve to a public address, checked on every delivery. Only 307 and 308 redirects 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, or bgm are 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.

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 the Request-Id of 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 videoUrl links.
  • Copies of source payloads without a retention policy.

Before production

  1. The production key exists, has the narrowest scopes and classrooms, and is in the production secret manager.
  2. Key rotation can be done without a code change.
  3. The webhook receiver verifies signatures and timestamps and skips duplicates.
  4. Logs carry key prefixes and ids, never secrets.
  5. One small production-like request has succeeded end to end.

Was this page helpful?