This page does not ask you to redraw the product. Move login, API keys, and payer identity to Daven. Leave the learning loop, brand, and copy with the app.
One line
The app owns learning UX and artifacts. Hub owns people and classrooms. Daven owns model execution and the school wallet. The browser never sees keys or payers.Three layers
@davenai/sdk/edu (browser) and @davenai/sdk/server (BFF) join the layers. Do not add login() on the education-app SDK. The public name is session.
Invariants
Do not copy this playbook into another repo if you break any of these.- Hub Start opens
{hostedUrl}?session=&proxy=&locale=in a new tab.localematches the Hub path (en|kr). If the app routes onko, mapkr→ko. Do not iframe the education app in the dashboard as the default launch. - Students and teachers sign in only on Hub. Do not add Daven / Google / an in-app student-code login as the Edu default.
- Never put a Daven key, MCP key,
OPENAI_API_KEY, or school payer ID in the browser or a public bundle. - The client does not choose
studentId,schoolId,classroomId, or payer. TrustGET /api/session/contextonly. - Edu-runtime AI uses the proxy only. If the session is missing or expired, fail closed with
EDU_RELAUNCH_REQUIRED. Do not fall back to an operator provider key. - Mint one stable
operationIdper user action. Retries derive from that id. Do not reuse the same idempotency key on a different body. - In-app save / publish / download is not Hub submit.
externalArtifactIdis the app artifact id. Only an explicit resubmit of the same id updates the Hub row. - If
shouldRelaunchEducationApp(error)is true, keep the work and tell the student to reopen from Hub. Auto-refresh is not the default. - Do not use Gemini (or any provider whose terms block under-18 clients) on the student path.
- Domain prompts, validation, and screens stay in the app. The SDK replaces transport only.
- The tab favicon is the Classroom Hub mark
/brand/classroom-logo.png. The tab title is the app name. Do not leave the Next / Vercel default icon.
Run modes
An already-shipping app should fork paths behind one flag. Do not flip the default during an active school term.
Use these names even if you only need one mode. The next app will search for them.
Local and Playground bill the developer. Classroom bills the school managed wallet. Never debit both on one request.
Ownership
- App keeps
- Hub takes
- Daven takes
- Product name, copy, learning loop
- Domain prompts and result checks
- Drafts, artifacts, progress (app DB or browser storage)
- App-only teacher tools (content review, progress board)
- Export formats (PDF, PPTX, ZIP)
edu_user_id into an existing user_id or daven_id column.
Minimum code
Browser:@davenai/sdk/server with the verified session and proxy. Do not pass a payer id into the SDK.
Domain routes see a gateway only.
Auth bridge
If the app already has a user table and RLS, follow Reading Passport.1
Launch
Hub opens
{hostedUrl}?session=&proxy= in a new tab. The SDK moves the query into headers and strips it from the URL.2
Bootstrap
The app server re-validates the signed session with
GET {proxy}/api/session/context. The client must not trust a decoded JWT.3
Local principal
Map
edu_actor_id to a deterministic in-app id. If you must keep existing FK/RLS, issue a non-login local principal. That is not a second account.4
Cookies
Keep session and proxy HttpOnly. The Edu
actorId on the request must always match the server-owned edu_actor_id.actorId as the runtime key. Drafts may stay in the browser. Only Hub submission URLs must be durable public HTTPS.
Never merge Edu users by name, student code, or email. The match key is actorId only.
teacher_manage must not impersonate a student. Turn on supportsTeacherManage in the catalog only when the app has a teacher workspace.
AI adapter
1
One boundary
Every generation goes through a gateway. Routes do not call the OpenAI or Gemini SDK directly.
2
Pin the runtime
Decide Edu vs legacy at the front of the request. Pass that runtime explicitly into
after() and workers. An Edu job must never fall onto the operator key because a cookie vanished in the background.3
Quote → create → poll
Images:
quote → media/create → media/get. Text: text/create. Do not invent a credit number that is not the Edu quote.4
Failure
Release the reservation on an explicit provider failure. On session expiry, keep the draft and ask for a Hub relaunch.
Submissions
Resubmitting the same work updates that row. A different id creates a new row. Omitting the id stores
__default__.
Copy generated images into non-expiring storage before submit. Do not put a 7-day cleanup URL in primaryUrl.
Host and tab icon
Copy this into the next app too.- The public URL is
https://{slug}.edu-app.daven.ai. DNS is one wildcard:*.edu-app.daven.ai→cname.vercel-dns.com. Do not put apps on*.edu.daven.ai— that zone is Hub /api/dev. - Add
{slug}.edu-app.daven.aion the Vercel project only. The wildcard DNS already exists. - The tab favicon is the Classroom Hub mark. Copy Hub
dv-edu/apps/web/public/brand/classroom-logo.pngtopublic/brand/classroom-logo.pngand set metadataicons.icon/icons.apple. - The tab title is the app name, for example Image Studio. The favicon is Edu. Do not put an app logo, Vibe, or the Next / Vercel default icon in the tab.
- Do not set
DAVEN_API_KEYon production Vercel. Class uses the Hub session only. The key is for localnext devand Playground development. - Playground Submit → Master Approve. Match
appSlug,appName, andhostedUrl. LeavesupportsTeacherManageoff unless the app has a teacher-only workspace. - Before catalog Approve, a student session must return one real text result and one real image. If you wrap, deploy, and register in one shot, the failures below show up first in class.
What broke on Image Studio
Do these in order or the next app will fail the same way.Quote VALIDATION_ERROR — slug or code is required
Quote VALIDATION_ERROR — slug or code is required
Hub
POST /api/quote accepts only { "media_type": "image" | "text" }. Forwarding that to daven.ai/api/mcp/quote makes PHP require slug/code and return VALIDATION_ERROR 422. The app surfaces it as “Could not make the picture.”Hub quote must return the school-pool fixed credits locally: image 10, text 1, billing: school-pool. It is not the Daven MCP model-registry quote.Quote is advisory. Production debit happens when text/create / media/create returns 2xx against school remaining_credits. Do not abort generate on a quote 422. Reading Passport skips quote.Omitting model selects nano-banana-2 (student Gemini)
Omitting model selects nano-banana-2 (student Gemini)
If
media/create has no model, Daven /image/create uses the workflow default nano-banana-2. You do not get gpt-image-2 from the docs.Classroom default is gpt-image-2.5. Set it on the app and on Hub. Do not use Gemini or nano-banana on the student path.gpt-image-2.5 quality is fast | quality. medium clamps to fast. Hub must forward quality to /image/create.Enhance EMPTY_RESULT
Enhance EMPTY_RESULT
text/create can return 200 with data.text === "". The app then returns EMPTY_RESULT 502. gpt-5-mini + reasoning_effort: minimal + a small max_output_tokens (for example 900) spends the budget on reasoning.Classroom text uses gpt-5.6-luna, at least 1600 output tokens, and no reasoning flag by default. Retry once with a derived operationId if the body is empty. Treat 200 + empty text as failure.Image succeeded, then died at 5MB
Image succeeded, then died at 5MB
Daven PNGs often exceed 5MB. Throwing
EDU_IMAGE_TOO_LARGE after create and billing leaves the student a 502. Allow about 25MB on download, or compress after fetch.Submit Too Large — posting page data URLs
Submit Too Large — posting page data URLs
Bundling finished page PNGs as data URLs into one
POST /api/studio/submit hits the Vercel function body limit (~4.5MB). Students see 413 FUNCTION_PAYLOAD_TOO_LARGE or “Too Large.”Upload each page through Hub upload-url, then POST only HTTPS primaryUrl + assets[]. The browser PUTs to storage; if CORS blocks that, fall back to one JPEG through the BFF. The Hub teacher Submissions tab opens those URLs.Production keys and Vercel traps
Production keys and Vercel traps
Do not set
DAVEN_API_KEY or OPENAI_API_KEY on production Vercel. Class uses the Hub session only. Local next dev with a developer key is not a student generate.The Vercel framework must be Next.js. Other 404s the first deploy. Bind only {slug}.edu-app.daven.ai.Framer onDrag on motion.button breaks the React 19 production build. Omit the drag props.Stuffing the launch JWT into cookies yields EDU_SESSION_TOO_LARGE. Keep cookies short. Read the session on the server.Phases (copy into the next app)
1
0. Contract
Confirm
expectedAppSlug, session v2 purposes, OpenAPI, and credits (text 1 / image 10).2
1. Auth
/edu/bootstrap or an equivalent server entry. Hide the login screen in Edu mode. An operator-only login may stay, split from classroom roles.3
2. Non-AI loop
Launch → save → close → reopen. Prove data survives without AI first.
4
3. Text AI
Move low-risk calls through the gateway, then structured output.
5
4. Image AI
Send
model: gpt-image-2.5. References, aspect ratio, job polling. No Gemini / nano-banana on the student path.6
5. Student smoke
One text and one image from Hub Start. Approve the catalog only after
VALIDATION_ERROR / EMPTY_RESULT / 5MB drops are gone.7
6. Submit
Durable URL + stable
externalArtifactId + an explicit button. No auto-submit.- The network tab has no browser calls to
daven.ai/app.daven.ai. - The same generate without a session returns 401 or relaunch — the operator key does not slip in.
- Clearing the session keeps the draft and only blocks AI.
- A teacher preview session cannot submit.
Do not
- Iframe Hub inside the app, or make a dashboard iframe the default app launch
- Show Daven marketing, profile, billing, or
auth.daven.aito students - Restyle the app as SEED Classroom Hub — SEED is Hub only
- Use the Vibe wordmark or Braid gradient as the education-app brand
- Leave the Next / Vercel default favicon in the tab — use Edu
classroom-logo.png - Put “platform / solution / API key” on a student button
