Use this page to look up every field of the registration, account, email verification, and billing routes an agent calls on its own.
Overview
These routes are called by agents, without a person. Registration and discovery
are public. The account and billing routes take the live
Content key that
POST /v1/platform/agent/register returns as contentApiKey, which has full access.
A test key or a narrower key gets 403
platform_content_key_too_narrow; a missing or unknown key gets 401
platform_invalid_api_key. A legacy tf_platform_ key also works on
these account routes, and on no content route.
On these routes, organizationId is the workspace
id, not the id of the TutorFlow organization
that owns the classrooms and the credit balance. Balances are in
credits, except creditBalance, which is in cents.
Content work itself happens on the Content API,
under /v1/content/classrooms/{classroomId}.
Base URL:
https://api.tutorflow.io/v1/platformGET /v1/platform/pricing-catalog
Return the live pricing catalog, generated from the credit amounts the backend charges at 2 US cents per credit. See Pricing and Billing for every entry.
Authentication
None.
Response Fields
The response is an array.
| Field | Type | Description |
|---|---|---|
category | string | Billable category: course, evaluation, game, module, slide, slide_image, test_creation, video_narration, or video_render |
tier | string | Current public tier. The live catalog uses default |
catalogKey | string | Stable catalog key for this category and tier |
amountUsd | number | USD per unit for a per-unit category, or the flat USD price for a flat one |
baseUsd | number | The flat part a per-unit category also charges, in USD; 0 otherwise |
unitCredits | number | Credits per unit; 0 for a flat category |
baseCredits | number | Flat credits charged once per request |
unit | string | Billable unit: lesson, request, game, text page, image page, test, scene, or render |
description | string | Which Content API call the entry prices, and how |
currency | string | USD |
source | string | organization_credit_amount_v3 |
stripeLookupKey | string | null | Lookup key of the metered Stripe price, the same for every entry |
stripeMeterEventName | string | null | agent_credits, the one Stripe meter overdraft is reported to |
isActive | boolean | Always true for entries returned by this endpoint |
GET /v1/platform/.well-known/agent.json
Return the discovery manifest agents can use to find registration, pricing, capability, add-on, and top-up metadata.
Authentication
None.
Stable Fields
| Field | Type | Description |
|---|---|---|
schema | string | Manifest schema URL |
name | string | Service name |
description | string | Service description |
baseUrl | string | The Content API base URL, e.g. https://api.tutorflow.io/v1/content |
accountBaseUrl | string | The account routes, e.g. https://api.tutorflow.io/v1/platform/agent |
auth.type | string | bearer |
auth.prefix | string | tf_content_ |
auth.testPrefix | string | tf_content_test_ |
registration.endpoint | string | /v1/platform/agent/register |
registration.requiredFields | string[] | name, slug |
registration.optionalFields | string[] | email, agentIdentity |
registration.returns | string[] | contentApiKey, defaultClassroomId, contentApiBaseUrl |
account.endpoint | string | GET /v1/platform/agent/account |
services | string[] | course, module, test, slide, video, game, evaluation |
capabilities | object | Content API endpoint (with {classroomId}), unit, and USD price per service |
addOns | object | video_narration and video_render, with their Content API routes and prices |
trial.creditsUsd | number | 1 |
trial.credits | number | 50 |
trial.centsPerCredit | number | 2 |
trial.emailVerificationRequiredForTrial | boolean | false |
trial.emailVerificationRequiredForBilling | boolean | true |
pricing.endpoint | string | /v1/platform/pricing-catalog |
paymentMethods.topUp.endpoint | string | POST /v1/platform/agent/billing/session |
paymentMethods.topUp.minimumUsd | number | 5 |
paymentMethods.topUp.maximumUsd | number | 5000 |
paymentMethods.usdc | object | depositAddress and payment, the two x402 routes |
shareRedirects.endpoint | string | GET /v1/platform/share-redirects/{shareToken}: public; answers { resourceType, url }, where an old Platform share link now lives. Limited to 60 requests per minute per IP |
docs | string | https://tutorflow.io/agent-platform/docs |
health | string | /v1/platform/health |
x402.enabled | boolean | true on production: agents can buy credits with USDC on Base |
x402.network | string | eip155:8453 (Base) |
x402.asset | string | USDC |
The manifest and GET /v1/platform/pricing-catalog quote prices from the same
source, and both price the Content API calls. In capabilities, creating a video
is free (unitPriceUsd 0); its narration and render are the video_narration
($0.02 per scene) and video_render ($0.10 per render) add-ons. capabilities.slide
carries imagePagePriceUsd (0.06) next to its text page price. See
Pricing and Billing
for every price.
POST /v1/platform/agent/register
Create an agent account and return its key. One transaction creates the
account, the TutorFlow organization behind it, a default classroom, and 50 trial
credits; then the tf_content_ key is issued.
Authentication
None. Register is limited to 5 attempts per client IP per rolling hour, and
failed attempts such as a 422 count too. Register once and keep the key.
Every answer carries X-RateLimit-* headers; over the limit the answer is 429
platform_rate_limit_exceeded with a Retry-After header and
error.retryAfterSeconds.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Workspace display name |
slug | string | Yes | Unique slug. Lowercase letters, numbers, and hyphens only. Cannot start or end with a hyphen |
email | string | No | Contact email. Trial works without it, but paid top-up cannot complete through public agent endpoints without an email |
agentIdentity | string | No | Free-form agent identity stored in the account metadata |
Example Request
curl -X POST https://api.tutorflow.io/v1/platform/agent/register \
-H "Content-Type: application/json" \
-d '{
"name": "My AI Tutor",
"slug": "my-ai-tutor",
"email": "agent@example.com",
"agentIdentity": "claude via mcp"
}'Response
{
"organizationId": "0f7a5db7-9f9d-4a46-9f5b-5b8f8ad73001",
"apiKey": null,
"keyPrefix": null,
"contentApiKey": "tf_content_...",
"contentApiKeyPrefix": "tf_content_a1b2c3d4",
"defaultClassroomId": "00000000-0000-4000-8000-000000000010",
"contentApiBaseUrl": "https://api.tutorflow.io/v1/content",
"trialCredits": 100,
"trialContentCredits": 50,
"message": "Store contentApiKey securely; it will not be shown again. It signs every Content API call and the account routes under /v1/platform/agent. apiKey is null: legacy tf_platform_ keys are no longer issued.",
"requiresEmailVerification": true,
"verifyEmailResendEndpoint": "POST /v1/platform/agent/verify-email/resend",
"nextSteps": {
"createCourse": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/courses",
"listCourses": "GET /v1/content/classrooms/00000000-0000-4000-8000-000000000010/courses",
"createSlide": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/slides",
"createVideo": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/videos",
"accountInfo": "GET /v1/platform/agent/account"
}
}| Field | Description |
|---|---|
organizationId | The workspace id. Not the TutorFlow organization id. No route needs it |
contentApiKey | The live tf_content_ key. Use it for everything |
defaultClassroomId | The classroom every Content API call uses: /v1/content/classrooms/{defaultClassroomId}/... |
contentApiBaseUrl | The Content API base URL for this environment |
trialContentCredits | Trial credits on the account: 50, worth $1.00 at 2 cents per credit |
trialCredits | The same trial grant in cents: 100 |
apiKey, keyPrefix | Always null. Register no longer issues legacy tf_platform_ keys; a legacy key an older account already holds still works on the account routes |
nextSteps | Content API routes to try first. createCourse creates an empty course; to have TutorFlow write one, use POST .../courses/generate |
Store contentApiKey immediately. It is returned only once.
Errors
| HTTP | error.code | Cause | Agent action |
|---|---|---|---|
422 | platform_invalid_request | Invalid slug, invalid email, or malformed body | Fix the request |
409 | platform_conflict | Slug or email is already registered | Choose a new slug, or reuse the account that has the email |
429 | platform_rate_limit_exceeded | More than 5 register attempts from this IP in the past hour | Wait Retry-After seconds, or reuse an existing account |
GET /v1/platform/agent/account
Return the account, its balance, its classrooms, and its rate limit.
Authentication
Live tf_content_ key.
curl https://api.tutorflow.io/v1/platform/agent/account \
-H "Authorization: Bearer tf_content_..."Response
{
"organizationId": "0f7a5db7-9f9d-4a46-9f5b-5b8f8ad73001",
"name": "My AI Tutor",
"slug": "my-ai-tutor",
"status": "ACTIVE",
"creditBalance": 88,
"credits": 44,
"overdraftCredits": 0,
"overdraftLimit": 100,
"isMetered": false,
"rateLimitPerMinute": 1000,
"isEmailVerified": false,
"restrictedActions": ["add_payment_method", "usdc_payment", "create_billing_session"],
"createdAt": "2026-10-16T09:00:00.000Z",
"defaultClassroomId": "00000000-0000-4000-8000-000000000010",
"sandboxClassroomId": null,
"contentApiBaseUrl": "https://api.tutorflow.io/v1/content",
"contentApiUrls": {
"listClassrooms": "GET /v1/content/classrooms",
"generateCourse": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/courses/generate",
"listCourses": "GET /v1/content/classrooms/00000000-0000-4000-8000-000000000010/courses",
"generateSlides": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/slides/generate",
"generateTest": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/tests/generate",
"createVideo": "POST /v1/content/classrooms/00000000-0000-4000-8000-000000000010/videos"
}
}| Field | Description |
|---|---|
credits | The prepaid balance, in classroom credits |
creditBalance | The same balance in US cents (credits x 2), not dollars: 44 credits answer 88 |
overdraftCredits | Credits drawn beyond the balance, waiting to be billed. Only on a metered account |
overdraftLimit | How far the account may overdraw once metered |
isMetered | true once a verified payment method lets the account overdraw |
rateLimitPerMinute | The account-route rate limit. Content API keys have their own limits |
organizationId | The workspace id |
defaultClassroomId | The classroom for Content API calls with a live key. null only for a workspace with no classroom yet |
sandboxClassroomId | The only classroom a test key reaches. null until the account has a test key |
contentApiBaseUrl | The Content API base URL |
contentApiUrls | The first Content API calls for the default classroom, or null |
restrictedActions lists action keys the agent cannot perform on
this account yet. While email verification is pending, billing-related actions
are gated. The array is empty once isEmailVerified is true.
GET /v1/platform/agent/billing/status
Return the balance and card status. It works even when the balance is empty.
Authentication
Live tf_content_ key.
curl https://api.tutorflow.io/v1/platform/agent/billing/status \
-H "Authorization: Bearer tf_content_..."Response
{
"hasPaymentMethod": true,
"cardBrand": "visa",
"cardLast4": "4242",
"creditBalance": 5000,
"credits": 2500,
"overdraftCredits": 0,
"overdraftLimit": 1000,
"isMetered": true,
"billingMode": "internal_invoice",
"isEmailVerified": true,
"restrictedActions": []
}cardBrand and cardLast4 are null when no Stripe card is attached.
restrictedActions is non-empty only while isEmailVerified is false:
adding a payment method, USDC payments, and creating a billing checkout
session all require a verified email. The balance fields mean the same as on
GET /v1/platform/agent/account.
POST /v1/platform/agent/verify-email/resend
Generate a new 24-hour verification token and send the verification email to
the account email (the email given at registration).
Authentication
Live tf_content_ key.
curl -X POST https://api.tutorflow.io/v1/platform/agent/verify-email/resend \
-H "Authorization: Bearer tf_content_..."Response
{
"message": "Verification email sent"
}Errors
| HTTP | error.code | Cause | Agent action |
|---|---|---|---|
200 | Email is already verified | The person who owns the email is added as an admin of the account, and gets a set-password email if they have no password. Continue to billing. | |
400 | platform_invalid_request | Email is already verified, but no TutorFlow user can be attached to it | Contact support |
400 | platform_invalid_request | No email address on file | Register a new account with email. No public route adds an email later |
GET /v1/platform/agent/verify-email
Verify the account email from the link in the verification email.
Authentication
None.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Email verification token from the verification email |
Response
This endpoint redirects to the frontend instead of returning JSON:
| Result | Redirect |
|---|---|
| Verification succeeded | /sign-in/platform?verified=true |
| Token invalid or expired | /sign-in/platform?verified=false |
Agents normally do not call this endpoint directly. A human recipient clicks
the email link, then the agent retries billing. Verifying also makes that person
an admin of the account's organization, so they can sign in to TutorFlow and
open the editorUrl of the account's content.
POST /v1/platform/agent/billing/session
Mint a one-hour TutorFlow checkout token and return a hosted checkout page URL. The agent forwards this URL to a human operator, who chooses a top-up amount and pays through Stripe.
Authentication
Live tf_content_ key. A verified email is required.
curl -X POST https://api.tutorflow.io/v1/platform/agent/billing/session \
-H "Authorization: Bearer tf_content_..."Response
{
"url": "https://tutorflow.io/agent-platform/checkout?token=6c7b1e8d-0b5a-4cb8-a1e4-0473f0001001",
"sessionId": "6c7b1e8d-0b5a-4cb8-a1e4-0473f0001001"
}sessionId is the TutorFlow checkout token. It is not a Stripe cs_...
session ID.
Errors
| HTTP | error.code | Cause | Agent action |
|---|---|---|---|
403 | email_verification_required | The account email is not verified | Call POST /v1/platform/agent/verify-email/resend, have the recipient click the link, then retry |
POST /v1/platform/agent/billing/x402/deposit
Create a USDC deposit address on Base for an amount. Available where x402 is enabled.
Authentication
Live tf_content_ key. A verified email is required.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amountUsd | number | Yes | The USD amount to deposit, from 5 to 5000. Credited at 2 cents per credit |
Response
{
"scheme": "exact",
"network": "eip155:8453",
"payTo": "0x...",
"amountUsd": 10,
"expiresInSeconds": 300,
"paymentUrl": "POST /v1/platform/agent/billing/x402"
}Pay exactly amountUsd in USDC to payTo, then call paymentUrl.
POST /v1/platform/agent/billing/x402
Verify a paid USDC deposit and credit the account at 2 cents per credit.
Authentication
Live tf_content_ key. A verified email is required.
Headers
| Header | Required | Description |
|---|---|---|
x-payment | Yes | The x402 payment payload for a deposit address from POST /v1/platform/agent/billing/x402/deposit |
Response
{
"isCredited": true,
"credits": 550,
"creditBalance": 1100
}Errors
| HTTP | error.code | Cause | Agent action |
|---|---|---|---|
402 | x402_payment_failed | The payment has not settled yet | Retry after a few seconds |
403 | agent_channel_only | The key belongs to a workspace that is not an agent account | Buy credits in TutorFlow Billing |
403 | email_verification_required | The email is not verified | Verify, then retry |
GET /v1/platform/agent/checkout
Validate a checkout token for the hosted checkout page and return the account's display name and email. The checkout page calls it; an agent does not.
Authentication
None.
Query Parameters
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | TutorFlow checkout token from POST /v1/platform/agent/billing/session |
Response
{
"email": "agent@example.com",
"name": "My AI Tutor"
}Errors
| HTTP | error.code | Cause |
|---|---|---|
400 | platform_invalid_request | Checkout token has expired |
404 | platform_not_found | Token is unknown or already used |
POST /v1/platform/agent/checkout
Create the actual Stripe Checkout session from a valid TutorFlow checkout token and selected top-up amount.
Authentication
None. This endpoint is intended for the hosted checkout page.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | TutorFlow checkout token from POST /v1/platform/agent/billing/session |
amountUsd | number | Yes | Top-up amount. Minimum 5, maximum 5000 |
Response
{
"url": "https://checkout.stripe.com/c/pay/cs_test_..."
}The checkout token is invalidated after a Stripe URL is created.
Errors
| HTTP | error.code | Cause | Agent action |
|---|---|---|---|
400 | platform_invalid_request | Amount is outside $5 to $5,000, or token is expired | Choose a valid amount or request a new billing session |
403 | email_verification_required | Email became unverified before checkout completion | Verify email, then request a new billing session |
404 | platform_not_found | Token is unknown or already used | Request a new billing session |
GET /v1/platform/health
Validate that a key can authenticate against the Agent Platform.
Authentication
Live tf_content_ key, or a legacy tf_platform_ key. It works even when the
balance is empty. Without a valid key it answers 401:
{
"error": {
"code": "platform_invalid_api_key",
"message": "Invalid or missing Platform API key",
"error": "Unauthorized",
"statusCode": 401,
"status": 401
}
}Response
{
"status": "ok",
"organizationId": "0f7a5db7-9f9d-4a46-9f5b-5b8f8ad73001"
}