Resources
Define forms, webhooks, and schedules as workflow entry points.
Forms collect input from people, webhooks accept HTTP requests, and schedules start work at specified times. Bind these resources to workflows with triggers. Public forms can use a basic-auth credential profile to restrict submissions.
Forms
A form defines an input interface with a JSON Schema for its payload. Swirls server-renders the form from that schema (each property's "title" becomes the field label) and hosts it at a public endpoint; submissions are CSRF-protected. You can inline the schema with @json { … } or define a reusable top-level schema block and set schema: my_schema_name on the form (and on workflow nodes) so one definition is shared across triggers and steps.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display name |
description | string | No | Description shown in the dashboard |
visibility | string | No | public or internal. Defaults to internal. |
enabled | boolean | No | Whether the form is active |
auth | identifier | No | Name of a top-level auth block with type: basic for HTTP Basic auth gating |
schema | json block or name | No | JSON Schema for the form payload, or a reference to a schema resource |
Visibility. public forms are served at /triggers/forms/:projectId/:formName. internal forms return 404 from the hosted endpoint but remain usable from the Cloud dashboard, where submitting still fires the trigger. The default is internal.
HTTP Basic auth. Set auth: <block_name> where the referenced auth block is a credential profile with type: basic. A valid Authorization: Basic header is then required before the hosted form renders or accepts a submission. This only applies to public forms: an internal form 404s on the hosted endpoint before any auth check, so auth: on it has no effect.
schema contact_payload {
label: "Contact payload"
schema: @json {
{
"type": "object",
"required": ["name", "email", "message"],
"properties": {
"name": { "type": "string", "title": "Name" },
"email": { "type": "string", "title": "Email" },
"message": { "type": "string", "title": "Message" }
},
"additionalProperties": false
}
}
}
form contact_form {
label: "Contact Form"
description: "Collect contact information"
visibility: public
enabled: true
schema: contact_payload
}A password-protected public form:
secret portal_auth {
vars: [PORTAL_USER, PORTAL_PASS]
}
auth portal_basic {
type: basic
secrets: portal_auth
username: PORTAL_USER
password: PORTAL_PASS
}
form partner_intake {
label: "Partner Intake"
visibility: public
auth: portal_basic
enabled: true
schema: contact_payload
}Webhooks
A webhook defines a hosted HTTP POST endpoint at /triggers/webhooks/:projectId/:webhookName.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display name |
description | string | No | Description |
enabled | boolean | No | Whether the webhook is active |
schema | json block or name | No | JSON Schema for the expected payload |
secret | <block>.<var> | No | Reference to a secret var for shared-secret verification |
header | string | No | Header name that carries the shared secret (e.g. "X-Webhook-Secret") |
Shared-secret auth. The sender puts the secret value in the configured header; Swirls compares it against your stored secret in constant time. A missing or wrong secret gets a uniform 401. Rules:
secretandheadergo together: setting one without the other is a validator error.- Without both, the webhook accepts any POST. That is allowed, but the validator warns so the choice is auditable.
- The header name must be a custom header. Standard headers that proxies or clients control (
Cookie,Host,Content-Type,X-Forwarded-*,User-Agent, and similar) are rejected;Authorizationis allowed.
Payload handling. An empty body is treated as {}. Invalid JSON gets 400. When a schema is declared, the payload is validated against it; a mismatch gets 422 with field-level errors. An accepted request gets 202 with the started execution ids. A disabled webhook returns 200 with an explanatory body so senders do not retry forever.
secret partner_creds {
vars: [PARTNER_WEBHOOK_SECRET]
}
webhook partner_events {
label: "Partner Events"
enabled: true
secret: partner_creds.PARTNER_WEBHOOK_SECRET
header: "X-Webhook-Secret"
schema: @json {
{
"type": "object",
"required": ["type", "data"],
"properties": {
"type": { "type": "string" },
"data": { "type": "object" }
},
"additionalProperties": false
}
}
}Schedules
A schedule triggers a workflow on a cron expression.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Display name |
cron | string | Yes | Standard 5-field cron expression |
timezone | string | No | IANA timezone (e.g. "America/New_York") |
enabled | boolean | No | Whether the schedule is active |
Semantics:
- Schedules fire with one-minute granularity. The
cronexpression is evaluated in the schedule'stimezone. - The payload is always
{}. A scheduled workflow's root should not expect input fields. - A schedule never overlaps itself: a run that is still going blocks the next occurrence, and the next run time is computed before firing, so a slow run does not delay the occurrence after it.
schedule daily_report {
label: "Daily Report"
cron: "0 9 * * *"
timezone: "America/New_York"
enabled: true
}Connecting resources to workflows
Resources do not run workflows on their own. You connect a resource to a workflow using a trigger. See Triggers for details.