Authenticate requests solely from the Authorization: Bearer header on the server; stop accepting the legacy `token` header and `usertoken` cookie. The frontend no longer sets auth cookies (only clears legacy ones), omits credentials on all requests, and drops the redundant `token` header. Project event SSE now streams over fetch so it can carry the Authorization header, which EventSource cannot. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.9 KiB
img-infinite-canvas API
Base path: /api
Health
GET /api/health
{
"status": "ok",
"service": "img_infinite_canvas-api",
"time": "2026-07-04T03:20:00Z"
}
Dashboard
GET /api/dashboard
Returns credits, quick actions, recent projects, and inspiration items for the Moteva-style home screen.
Auth
Global mode is enabled in etc/config.yaml. Email verification codes are stored through the cache abstraction for 10 minutes; with Cache.Driver: redis, the code only lives in Redis until TTL or successful consumption.
SMTP is configured under Auth.Email:
Auth:
Turnstile:
Enabled: true
SiteKey: 1x00000000000000000000AA
SecretKey: 1x0000000000000000000000000000000AA
SecretKeyEnv: TURNSTILE_SECRET_KEY
SiteVerifyURL: https://challenges.cloudflare.com/turnstile/v0/siteverify
TimeoutSeconds: 5
Email:
Driver: smtp # none|smtp
Host: smtp.example.com
Port: 587
Username: user@example.com
Password: app-password
FromName: hello
FromEmail: hello@example.com
BrandName: Moteva
UseTLS: false
StartTLS: true
TimeoutSeconds: 10
When Auth.Turnstile.Enabled is false, /api/auth/options returns turnstile.enabled=false; the frontend hides the Cloudflare Turnstile widget and sends email-code requests without a Turnstile token. When enabled, the frontend must submit turnstileToken, and the API validates it before creating or emailing a verification code.
POST /api/auth/global/email-code
{
"email": "user@example.com",
"turnstileToken": "client-widget-token"
}
Development response includes an email preview so the frontend can render the same verification flow without a mail provider:
{
"target": "user@example.com",
"expiresIn": 600,
"devCode": "263081",
"email": {
"fromName": "hello",
"fromEmail": "hello@auth.moteva.local",
"toEmail": "user@example.com",
"subject": "Welcome to Moteva!",
"code": "263081"
}
}
POST /api/auth/global/email-login
{
"email": "user@example.com",
"code": "263081"
}
Returns an authenticated session with accessToken, refreshToken, and the user profile. Project creation, generation, upload, save, and deletion require Authorization: Bearer <accessToken>.
POST /api/auth/global/google-login
{
"idToken": "google-id-token"
}
POST /api/auth/china/wechat-login
First QR callback:
{
"code": "wechat-oauth-code",
"state": "signed-state"
}
If the WeChat identity is already bound to a phone number, the response is authenticated. If not, the response has status: "phone_binding_required" and a short-lived bindingToken.
Phone binding step:
{
"bindingToken": "signed-binding-token",
"phone": "13800138000",
"countryCode": "+86",
"smsCode": "123456"
}
After binding succeeds, later QR logins use only code and state.
Account
All account endpoints require Authorization: Bearer <accessToken>. Cookie auth and the legacy token header are not used. Account profile state is owned by the auth module and persisted in PostgreSQL or the configured in-memory store.
GET /api/account/profile
Returns the current authenticated user.
PATCH /api/account/profile
{
"name": "Xu Liang",
"avatarUrl": "https://cdn.example.com/avatar.webp"
}
Updates account display name and avatar URL. The frontend uploads avatar files through /api/assets/upload-url first, then stores the returned public URL on the profile.
GET /api/account/devices
Returns the current local web session. This version does not maintain a multi-device session registry yet, so the current device is read-only and cannot be removed.
DELETE /api/account/devices
Removes non-current devices when a future server-side session registry exists. In the current local-only implementation, it returns {"removed": 0}.
POST /api/account/logout
Records a logout request server-side and returns {"removed": 0}. The frontend then clears its locally stored session.
Projects
GET /api/projects
Returns all project summaries, sorted by updatedAt descending.
POST /api/projects
{
"title": "still day 官网",
"prompt": "为 still day 品牌服装独立站设计官网视觉方向",
"imageModel": "gpt-image-2",
"imageSize": "1024x1024",
"enableWebSearch": true
}
enableWebSearch only opens the web-search capability. The agent model still decides per request whether search is actually needed; if it is not needed, no web-search step is recorded. When search runs, result titles, URLs, snippets, and cleaned page-content excerpts are stored for the agent.
GET /api/projects/:id
Returns the full project, including viewport, nodes, connections, and agent messages.
POST /api/canva/project/queryProject
{
"projectId": "project-id",
"cid": "1781596271730ngdozsck"
}
Returns a Lovart-style project document with assetMeta, compressed canvas, validProjectId, projectType, version, and permission metadata. The frontend uses this for lightweight project opening, then decodes the SHAKKERDATA:// canvas locally.
PATCH /api/projects/:id/title
{
"title": "夏季活动主视觉"
}
Renames the infinite canvas project and updates updatedAt.
DELETE /api/projects/:id
Deletes the project record. PostgreSQL cascades canvas nodes, chat messages, history, and connections; after the database delete succeeds, image assets referenced by the canvas and generated-file history are queued for asynchronous OSS deletion.
Agent Chat
POST /api/projects/:id/agent/chat
{
"messages": [
{
"role": "user",
"contents": [{ "type": "text", "text": "继续优化首页首屏,但保留品牌色" }]
}
],
"enableWebSearch": false,
"imageModel": "gpt-image-2",
"imageSize": "1024x1024",
"mode": "design"
}
The harness builds short memory from the current turn, recent messages, and visible canvas state, and long memory from durable project decisions, prior artifacts, and previous research records with page-content excerpts. Image titles and assistant language are generated from the request context instead of fixed model labels.
Generate
POST /api/projects/:id/generate
{
"prompt": "继续扩展产品详情页模块",
"mode": "design"
}
Returns the updated project, the assistant message, and generated nodes.
The generation path uses the configured design agent runner. The default is the sky-valley/pi Go agent adapter, with local desktop/MCP agent behavior disabled.
Chat History Replay
GET /api/projects/:id/history
Returns replayable assistant/user chat history for the project.
{
"projectId": "project-id",
"messages": []
}
Lovart-style agent thread endpoints are also available:
POST /api/canva/agent/queryAgentLastThread
Returns { "code": 0, "msg": null, "data": "<thread-id>" }.
POST /api/canva/agent/agentThreadList
Accepts { "projectId": "project-id", "page": 1, "pageSize": 100, "cid": "..." } and returns paginated thread summaries.
GET /api/canda/chat-history?thread_id=<thread-id>&project_id=<project-id>
Returns Lovart-style thread history items grouped by thread_meta.
GET /api/canda/thread/status?thread_id=<thread-id>&project_id=<project-id>
Returns { "code": 0, "msg": "success", "data": { "status": "done" } } or the current project thread status when project_id is present.
Save Canvas
POST /api/canva/project/saveProject
PATCH /api/projects/:id/canvas
{
"canvas": "SHAKKERDATA://<gzip-base64-canvas-snapshot>",
"projectCoverList": ["https://cdn.example.com/canvas/image.png"],
"picCount": 1,
"projectName": "Untitled",
"projectId": "project-id",
"version": "1783472149000",
"sessionId": "browser-session-id",
"canvasV2Gray": true
}
{
"code": 0,
"msg": null,
"data": {
"projectId": "project-id",
"newCreated": false,
"validProjectId": true,
"version": "1783498482000"
}
}
Saves canvas layout changes with a Lovart-style lightweight payload. The frontend posts to /api/canva/project/saveProject; /api/projects/:id/canvas remains as a compatibility route. The canvas value is a SHAKKERDATA:// gzip+base64 encoded snapshot containing viewport, nodes, and connections. Legacy uncompressed { "viewport": ..., "nodes": ..., "connections": ... } payloads are still accepted.
When a canvas image node disappears from the saved node list, its backing OSS asset is queued for asynchronous deletion after the canvas save succeeds.
Image Asset Upload
POST /api/assets/upload-url
{
"fileName": "reference.png",
"contentType": "image/png",
"size": 123456
}
Returns a signed upload URL for the configured storage backend.
Supported drivers:
memoryminios3/awsr2aliyun
Errors
JSON error response:
{
"error": "project not found",
"code": 404
}
Common status codes:
400: invalid input404: project not found500: internal server error