Resources
Credit History and Audit Log

Credit History and Audit Log

Read what each Content API key spent, and what each key and admin changed, with the credit history and audit log routes.

On this page

Two read routes answer the questions that come up once an integration runs in production: what did this key spend, and what did it change?

MethodPathScopeReturns
GET/v1/content/credits/historycontent:readAI Credit charges, refunds, and adjustments, newest first.
GET/v1/content/audit-logcontent:readChanges made with keys and by admins, newest first.

An admin signed in to TutorFlow reads the same data at /v1/content/organizations/{organizationId}/credits/history and /v1/content/organizations/{organizationId}/audit-log, with the same query parameters and responses. Settings > Content API shows both. For signing in, see Create a key with the API.

Who sees which rows

CallerSees
A full-access key: no classroom allowlist, and either no scope list or all of content:read, content:write, content:generate, and webhooks:manageEvery row of the organization, including rows from the TutorFlow app and from other keys. Narrow with apiKeyId.
A test keyOnly its own rows, even with every scope. Its credit history is always empty, because test mode never spends.
Any other keyOnly its own rows. Sending its own apiKeyId works; another key's id answers 403 content_forbidden.
An admin sessionEvery row of the organization.

A full-access key already can act on everything in the organization and read its balance, so it can also read everyone's history. learners:read is not needed for either route.

Paging

Both routes page with a cursor, newest first by createdAt, then id:

ParameterNotes
limitDefault 20. Values above 100 are clamped to 100.
cursormeta.nextCursor from the previous page. Send the same filters with it.
fromISO 8601 date-time. Rows at or after it.
toISO 8601 date-time. Rows before it.
apiKeyIdRows of one key.
JSON
{
  "data": [],
  "meta": { "limit": 20, "hasNextPage": true, "nextCursor": "eyJjIjoiMjAyNi0xMC0wMVQwOTowMDowMC4wMDBaIn0" }
}

Keep requesting with cursor set to meta.nextCursor while meta.hasNextPage is true; nextCursor is null on the last page. Rows written while you page do not shift your pages. A cursor that is malformed, or from another list or another set of filters, answers 422 content_invalid_request with a details entry for cursor: start again without it. These routes have no page, items, totalCount, or take.

Credit history

bash
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/credits/history?limit=50&from=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
JSON
{
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000c01",
      "createdAt": "2026-10-01T10:00:00.000Z",
      "type": "CREATE_GAME_BUILD",
      "kind": "charge",
      "amount": 40,
      "note": null,
      "classroomId": "00000000-0000-4000-8000-000000000010",
      "apiKeyId": "00000000-0000-4000-8000-000000000020",
      "apiKey": { "id": "00000000-0000-4000-8000-000000000020", "name": "cms-sync-production", "keyPrefix": "tf_content_abc123def456" }
    }
  ],
  "meta": { "limit": 50, "hasNextPage": false, "nextCursor": null }
}
FieldMeaning
typeWhat the row is for, for example CREATE_GAME_BUILD, CREATE_VIDEO_RENDER, CREATE_TEST, REFUND, or PURCHASE.
kindcharge (spent on an AI action), refund (given back for work that failed), or adjustment (purchases, grants, plan recharges, and corrections).
amountAlways positive. kind gives the direction.
noteExtra context, or null. A course generation outline carries Content API generation run {runId}: course outline.
classroomIdThe classroom the action ran in, or null.
apiKeyId, apiKeyThe key that made the charge, with its name and keyPrefix. null for rows from the TutorFlow app. A deleted key is still named. Rows from before 2026-10-01 have no apiKeyId.

Filter with type, one of the organization credit types: CREATE_COURSE, UPDATE_COURSE, CREATE_LESSON, UPDATE_LESSON, TRANSLATE_LESSON, OCR_SCAN, CREATE_IMAGE, CREATE_QUIZZES, CREATE_PROBLEMS, CREATE_AUDIO, CREATE_TEST, CREATE_CURRICULUM, CREATE_SLIDE, TOOL_COMPARE_AI, TOOL_VIDEO_GENERATION, DETECT_AI_GENERATED, TOOL_ASSIGNMENT_FEEDBACK, CREATE_VIDEO_SCRIPT, CREATE_VIDEO_SCRIPT_SPLIT, CREATE_VIDEO_TTS, CREATE_VIDEO_CLIP, CREATE_VIDEO_RENDER, CREATE_SLIDE_OUTLINE, CREATE_GAME_BRIEF, CREATE_GAME_BUILD, CREATE_GAME_REVISION, CREATE_SIMULATION_BRIEF, CREATE_SIMULATION_BUILD, CREATE_SIMULATION_REVISION, CREATE_SIMULATION_BUILD_3D, CREATE_SIMULATION_REVISION_3D, PROMO_CODE, PROMO_CODE_REVOCATION, PURCHASE, MONTHLY_RECHARGE, YEARLY_RECHARGE_BY_MONTH, REFUND, CANCELLATION, FAILED_OVERDRAFT_COLLECTION, or EMAIL_VERIFICATION. Any other value answers 422.

Only charges made with a key are attributed to it. What people do in the TutorFlow app never counts toward a key, even when the same person owns the key. To limit what a key can spend, set a monthly credit limit.

Audit log

bash
curl -sS "$TUTORFLOW_API_BASE_URL/v1/content/audit-log?resourceType=course&limit=50" \
  -H "Authorization: Bearer $TUTORFLOW_CONTENT_API_KEY"
JSON
{
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000d01",
      "createdAt": "2026-10-01T09:12:00.000Z",
      "apiKeyId": "00000000-0000-4000-8000-000000000020",
      "apiKey": { "id": "00000000-0000-4000-8000-000000000020", "name": "cms-sync-production", "keyPrefix": "tf_content_abc123def456" },
      "actorUserId": null,
      "classroomId": "00000000-0000-4000-8000-000000000010",
      "method": "PATCH",
      "route": "/v1/content/classrooms/:classroomId/courses/:id",
      "resourceType": "course",
      "resourceId": "00000000-0000-4000-8000-000000000201",
      "statusCode": 412,
      "idempotencyKey": null,
      "requestId": "3f0c2a9e-6b1d-4c8e-9f2a-7d5b1e0c4a11",
      "deprecatedFields": []
    }
  ],
  "meta": { "limit": 50, "hasNextPage": false, "nextCursor": null }
}
FieldMeaning
apiKeyId, apiKeyThe key that sent the request, or null for an admin's change in Settings.
actorUserIdThe admin who changed a key or a webhook in Settings or with an admin session, or null.
actorNameAdmin session route only: that admin's full name, or their username when they have none, or null. The key route never names staff.
method, routeThe HTTP method and the route template.
resourceType, resourceIdWhat was touched: course, test, module, slide, video, game, simulation, learner, webhook, or api_key.
statusCodeThe response status. Failed changes are recorded too.
idempotencyKeyThe Idempotency-Key sent, or null.
requestIdThe Request-Id of the response. Quote it to support.
deprecatedFieldsDeprecated fields the response carried, for example items.

Filter with resourceType and classroomId, besides the paging parameters above.

What is recorded

  • Every request with a key that changes something (every method other than GET), whether it succeeded or failed.
  • Every request to a learner route, reads included.
  • Key and webhook changes an admin makes in Settings or with an admin session.
  • Reads that received a deprecated field, sampled about once an hour per key and route, so you can find the code that still reads them.

Other reads are not recorded. Rows are kept for 90 days.

Was this page helpful?