Add canva/canda-compatible routes for lightweight project open/save and agent thread history: - POST /canva/project/queryProject, saveProject - POST /canva/agent/queryAgentLastThread, agentThreadList - GET /canda/chat-history, /canda/thread/status Canvas save now accepts a compressed SHAKKERDATA:// snapshot payload, preserves connections, and returns a SaveProject response; blank successful image nodes are backfilled from generated artifacts. Frontend designGateway adopts the new save/query flow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8.8 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 this login token.
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 the bearer token returned by login. 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 local access token and cookies.
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