Topolo Docs Topolo Platform
Open workspace

List forms in an authorized workspace

Discover workspace form ids and full form definitions, with separate submission counts and clear collection limits.

Outcome

Discover workspace form ids and full form definitions, with separate submission counts and clear collection limits.

GET /api/forms

Before you start

Use the original authorized identity and intended organization. Discover a canonical Forms workspace and bind it as separate workspace resource context. The published application grant is workspace:read; an organization id, form id or arbitrary workspaceId input is not a grant.

These are authenticated administrative reads, distinct from the public form-response route. User bearer requests resolve a required workspace with read access; API-key requests rely on Auth introspection of the same supplied resource binding and require that binding. Preserve any refusal rather than widening context.

Examples are complete source-derived synthetic responses, not captured production reads. No real form, respondent, answer, key material, consent, CRM, delivery, provider/model or notification operation was executed. Source-to-serving association and browser/UI proof remain separate.

Request and response

Action: app_topolo_forms.forms.list. Use {} for a list, or the discovered formId for a form/summary read; bind the authorized workspace separately. Validate current published input before the read. Fixture ids authorize no operation.

Returns HTTP200 {forms:[...]} from all selected-workspace form rows ordered by updated_at descending. The selected query has no LIMIT, cursor, pagination, total or stable tie-breaker. A structural catalog overlay permits other carriers, but this handler returns the forms envelope.

Each listed row is unprotected and mapped to the full form model: ids, organization/workspace, content, field definitions, creator/timestamps/status and submissionCount. Listing is not just names and ids. The model parses stored fields_json with [] fallback on invalid JSON; it does not normalize or revalidate every field on this read.

The implementation performs a separate submissions COUNT for each returned form, with those count reads in parallel, then maps rows. This can become expensive for large collections; no bounded batch or snapshot consistency is promised. Server-Timing names guard, workspace, rows, counts and decode stages but does not prove end-to-end latency.

Protection and consistency

The original organization and verified workspace scope every selected form and submission query. API-key/bearer handling and required workspace read access precede the concrete route permission check. A permitted read does not authorize edits, publishing, submission, export, delivery retry, erasure or key changes.

The selected source unprotects stored content under its environment-aware protection policy before mapping the response. A protection/key/dependency failure is not an empty result or permission to change environment policy, provision keys, migrate data or expose ciphertext as readable content. The guide does not certify production key health.

Reads can expose decrypted form text, field definitions and author metadata; summaries also load submission records internally. Keep actual returned content within its approved audience. Publishing this technical guide with fictional data does not make the application data public.

Independent form, count and submission reads are not a single cross-store transaction. A missing count row is converted to zero by source; zero by itself is not a protection, collection-health or completeness check. Shared runtime failures can differ from the concrete route errors below; no unique carrier is invented.

Complete worked responses

One form with a separate submission count

Complete synthetic, unrun HTTP200. Assumes the authorized fictional workspace contains exactly this form row, readable stored content and a separate submission count of3. Stored thank-you copy is explicit; no deployed translation default is assumed.

Input:

{}

Response:

{
  "forms": [
    {
      "id": "forms_docs_fixture",
      "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": "published",
      "fields": [
        {
          "id": "topics",
          "label": "Topics",
          "type": "multi_select",
          "required": false,
          "options": [
            "Docs",
            "API"
          ],
          "width": "full"
        }
      ],
      "createdByUserId": null,
      "createdByEmail": null,
      "createdAt": "2026-10-01T08:00:00.000Z",
      "updatedAt": "2026-10-20T08:00:00.000Z",
      "publishedAt": "2026-10-02T08:00:00.000Z",
      "submissionCount": 3
    }
  ]
}

Retain the actual returned form id and workspace for the intended follow-up. A list result is not approval to read answers or publish the form.

No selected-workspace form rows

Complete synthetic, unrun HTTP200 after successful original auth, workspace/permission checks and successful empty row query. This does not represent a swallowed dependency or decryption failure.

Input:

{}

Response:

{
  "forms": []
}

Treat this as no rows returned by this read, without creating a workspace/form to make an example nonempty.

Route permission refused

Complete synthetic, unrun HTTP403 after successful outer authentication and workspace selection but failed workspace:read route permission check. No live denial was induced.

Input:

{}

Response:

{
  "error": "forbidden"
}

Preserve the original context and actual refusal; use the owning administrator for legitimate access, not an alternate identity or guessed workspace.

Verify and recover

  1. Compare each returned organizationId/workspaceId with the intended authorized context. Use the returned id for forms.get only when that follow-up is required and permitted.

  2. Do not infer pagination, a total, stable ordering of equal timestamps, unique visitors or a cross-store snapshot. Reconcile a material submission-count question with the supported authorized form/submission reads.

  3. On an actual shared auth, protection or database failure preserve the request id, status and sanitized error. Do not interpret failure as an empty result, disable protection, migrate/rotate keys or run business actions as a read recovery.

These reads make no form or submission mutation and need no data rollback. A published guide is not a grant to operate on any sample or real record.

Refusals

403 / organization_required: Authenticate in the intended original organization; do not guess a tenant id or change context to force access.

400 / workspace_required: Supply a discovered authorized canonical workspace as separate resource binding; an action input id alone does not grant access.

403 / forbidden: The selected route permission check refused workspace:read. Preserve the actual denial and seek legitimate access from its owner.

Concrete route carriers are distinguished from generic action metadata and shared runtime errors. Invalid local input is a schema-validation refusal, not a captured server response.

Change log

  • 2026-10-21: Added full-model list behavior, unbounded ordering, per-form count reads, protection boundaries and complete illustrative populated/empty/refused responses. All examples remain synthetic and unrun.