Topolo Docs Topolo Platform
Open workspace

Create a draft form

Create a new scoped draft with protected content, then verify its actual id and definition before any publication.

Outcome

Create a new scoped draft with protected content, then verify its actual id and definition before any publication.

POST /api/forms

Before you start

Use the original authorized organization and discovered canonical Forms workspace, bound separately from input. Discover the real form through an authorized read when applicable. The published action requires workspace:write and the selected route requires read_write workspace access; sample ids are not grants.

Obtain approval for the actual mutation, target content and described effects. Validate the exact current action input and confirmed executable plan before a real call. This guide and its fixtures authorize no form change, public exposure, deletion, submission or notification.

All examples are complete source-derived synthetic responses, unrun. No real form/subject/protection/key/mirror/content/submission/provider/model/delivery/notification operation was executed. Source declaration matches the reviewed immutable revision; loaded runtime, dependency health, public rendering and actual delivery remain unverified.

Request and effects

Action: app_topolo_forms.forms.create.

Requires workspace:write and verified read_write workspace access. The route ensures the identity-only local mirror for an already authorized workspace before creating the form. Even a later input refusal can follow that mirror write; this does not create a new canonical Auth workspace.

Input requires a nonblank trimmed title. Published action status is draft only; status:published is a local schema refusal and must use the separate publish action. Fields are normalized; omitted fields produce a translated fallback question, while explicit empty arrays are rejected by the current schema and handler. Supplied thank-you copy avoids relying on a particular loaded translation.

Source creates a fresh forms_UUID, derives a slug from supplied slug/title with lowercasing/normalization and maximum80 characters, checks global slug availability up to20 attempts and then adds a random suffix. This is not an atomic reservation or retry idempotency contract. Preserve actual returned id/slug instead of predicting them.

Protected fields include title,description,thank-you text,fields JSON and creator email. Their writes use the existing environment-aware protection/key infrastructure. The selected insert is followed by a separate scoped getForm, which includes another submission count/decode. Failure after insertion can leave a created form; lost/invalid read-back is not proof of no effect.

HTTP201 returns {forms:<full object>}, a draft model with creator/timestamps and submissionCount. No publication, public response submission, CRM delivery or direct creation notification is performed by this selected route. Subsequent release/integration availability needs separate verification.

Complete worked responses

Draft inserted

Complete source-derived synthetic, unrun HTTP201. Assumes original authorized fictional workspace, successful mirror/protection/insert/read-back, the shown synthetic UUID/clock, available requested slug and no submissions. The explicitly supplied long_text feedback field is normalized without an unnecessary full-width field; stored thank-you copy is retained. No actual form/key/workspace operation was run.

Input:

{
  "title": "Fixture feedback",
  "description": "Synthetic documentation example",
  "thankYouTitle": "Thank you",
  "thankYouMessage": "Your fixture response was received.",
  "slug": "fixture-feedback",
  "fields": [
    {
      "id": "feedback",
      "label": "Feedback",
      "type": "long_text",
      "required": false
    }
  ]
}

Response:

{
  "forms": {
    "id": "forms_00000000-0000-4000-8000-000000000002",
    "organizationId": "org_docs_fixture",
    "workspaceId": "ws_forms_docs_fixture",
    "title": "Fixture feedback",
    "description": "Synthetic documentation example",
    "thankYouTitle": "Thank you",
    "thankYouMessage": "Your fixture response was received.",
    "slug": "fixture-feedback",
    "status": "draft",
    "fields": [
      {
        "id": "feedback",
        "label": "Feedback",
        "type": "long_text",
        "required": false
      }
    ],
    "createdByUserId": "usr_docs_fixture",
    "createdByEmail": "creator@example.test",
    "createdAt": "2026-10-21T01:00:00.000Z",
    "updatedAt": "2026-10-21T01:00:00.000Z",
    "publishedAt": null,
    "submissionCount": 0
  }
}

Retain actual original target/request evidence and perform the verification/recovery below. The fixture is not a live outcome or authority.

Whitespace title refused

Complete source-derived synthetic, unrun HTTP400. This input passes the string minLength schema but trims empty in the handler. Workspace mirror preparation can already have happened; no form insert is reached.

Input:

{
  "title": " "
}

Response:

{
  "error": "title_required"
}

Retain actual original target/request evidence and perform the verification/recovery below. The fixture is not a live outcome or authority.

Verify and recover

  1. Read forms.get for the actual returned id in the same original workspace. Compare full intended content, draft status, slug and creator; keep source/default normalization distinct from raw input.

  2. After a lost response, discover actual matching forms in the original workspace and reconcile identity/slug/content before any approved repeat. Fresh UUID/slug fallback can create duplicates; do not replay blindly or delete an ambiguous match.

If separately approved and the exact newly created form is verified, plan deletion after assessing submissions/deliveries/settings and retained exports. Deletion is destructive, not an exact undo of mirror/key/identity/history effects; never delete a guessed or concurrently used form.

Refusals and uncertainty

400 / workspace_required: Discover and bind the canonical authorized workspace through supported Auth resource/workspace actions, not a guessed id or app-bootstrap grant.

403 / forbidden: Preserve the actual write permission refusal. Seek legitimate access through its owner rather than broadening context.

400 / title_required: The handler needs a nonblank trimmed title. A whitespace-only schema-valid string is refused; invalid local input is distinct from a server response.

Protection, database, Auth and shared runtime failures need their actual status/request evidence. Inserts/updates and final read-back are separate; a lost, invalid or missing read-back is not proof of no committed effect. Do not disable protection, replay credentials or probe another tenant to recover.

Change log

  • 2026-10-21: Added scoped lifecycle behavior, protection/concurrency boundaries, exact illustrative success and refusal bodies, side-effect-aware verification and honest compensation limits. All examples remain synthetic and unrun.