Files
moteva/server/API.md
T
root a39e18950c refactor(auth): require bearer token only, drop legacy token header and cookie
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>
2026-07-09 01:09:39 +08:00

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:

  • memory
  • minio
  • s3 / aws
  • r2
  • aliyun

Errors

JSON error response:

{
  "error": "project not found",
  "code": 404
}

Common status codes:

  • 400: invalid input
  • 404: project not found
  • 500: internal server error