MCP 서버

furrow-forms

com.furrowforms/furrow-forms

이 MCP로 할 수 있는 일

Provisions and manages form backends for websites, including form definitions, embed snippets, submissions, email notifications, spam controls, uploads, and signed webhooks.

archive_client
Archive client
Use to archive a client. Do not use this as a hard delete. There is no unarchive tool in v1.
파괴적 작업 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['client_id'], 'properties': {'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Client id from list_clients or create_client.'}}}
archive_form
Archive form
Use to archive a form so public submit returns 404. Do not use this as a hard delete.
파괴적 작업 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['form_id'], 'properties': {'form_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Form id from list_forms, create_form, or bootstrap_site.'}}}
archive_project
Archive project
Use to archive a website. Archived projects reject public submit. Do not use this as a hard delete. There is no unarchive tool in v1.
파괴적 작업 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['project_id'], 'properties': {'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}}}
bootstrap_site
Bootstrap site
Use this to stand up a client, one website (project), and its forms in a single call. name/slug are the client; optional project_name/project_slug default to the same. Free accounts cannot create a fourth active project — call create_upgrade_link if bootstrap_site returns plan_limit. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Idempotent on client, project, and form slugs. Site settings (domains, Turnstile, notify emails, webhook, email templates, uploads) live on the project. Email templates support {{project.name}}, {{project.slug}}, {{form.name}}, {{form.slug}}, {{count}} (1-based clean submissions on this form, including the current one), and any submission field such as {{name}} or {{email}}. Use a fallback with {{name | "New submission"}}. Missing fields render empty and never error. Do not pass HTML; Furrow renders one owned layout. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. Checkbox groups, select multiple, and other multi-value HTML fields must use a [] suffix on the name (name="services[]") or only the last checked value is stored. Put services[] on field_contract.name too. JSON/fetch should send a real array without brackets ({ "services": ["a", "b"] }) and must not use Object.fromEntries(formData) — use formData.getAll("services"). Single checkbox, select, or combobox: no brackets. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet. Do not use when you only need to patch an existing form — call update_form.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['name', 'slug'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Client (company) display name.'}, 'slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Lowercase hyphenated identifier, unique per team. Used for idempotent re-runs.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Plain-text paragraph above the submitted fields. Supports {{tokens}}. Null removes it.'}, 'subject': {'type': 'string', 'maxLength': 300, 'minLength': 1, 'description': 'Notification email subject. Supports {{tokens}} such as {{form.name}} or {{name}}.'}, 'from_name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Display name on the From line of notification emails.'}, 'footer_mode': {'enum': ['furrow', 'minimal', 'off'], 'type': 'string', 'description': 'furrow shows the Furrow footer, minimal shows a one-line footer, off hides it (yearly plans only).'}, 'include_meta': {'type': 'boolean', 'description': 'Include submission metadata (IP, user agent, referrer, timestamp) below the fields.'}}, 'description': 'Notification email template for the project: subject, from_name, intro, footer_mode, include_meta.'}, 'forms': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'slug'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Form display name, e.g. Contact or Quote request.'}, 'slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Lowercase hyphenated identifier, unique per project. Used for idempotent re-runs.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Per-form intro override. Supports {{tokens}}. Null falls back to the project intro.'}, 'subject': {'anyOf': [{'type': 'string', 'maxLength': 300, 'minLength': 1}, {'type': 'null'}], 'description': 'Per-form subject override. Supports {{tokens}}. Null falls back to the project subject.'}}, 'description': 'Per-form subject and intro overrides for the notification email.'}, 'fields': {'type': 'array', 'items': {'type': 'object', 'required': ['name', 'type'], 'properties': {'name': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Posted field name. HTML checkbox groups / multi-select must end in [] (services[]) or only the last value is stored. JSON arrays omit the brackets.'}, 'type': {'type': 'string', 'maxLength': 40, 'minLength': 1, 'description': 'text, email, textarea, file, etc. type=file emits a file input in get_snippet.'}, 'label': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Human label used in the snippet and notification email. Defaults to the field name.'}, 'report': {'type': 'boolean', 'default': False, 'description': 'Include this field in dashboard summaries and reports.'}}}, 'description': 'Shorthand for field_contract: the fields this form posts.'}, 'field_contract': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['name', 'type'], 'properties': {'name': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Posted field name. HTML checkbox groups / multi-select must end in [] (services[]) or only the last value is stored. JSON arrays omit the brackets.'}, 'type': {'type': 'string', 'maxLength': 40, 'minLength': 1, 'description': 'text, email, textarea, file, etc. type=file emits a file input in get_snippet.'}, 'label': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Human label used in the snippet and notification email. Defaults to the field name.'}, 'report': {'type': 'boolean', 'default': False, 'description': 'Include this field in dashboard summaries and reports.'}}}}, {'type': 'null'}], 'description': 'Declared fields (name, type, label, report). Drives get_snippet and the email layout. Null clears it.'}, 'webhook_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Webhook endpoint for this form only. Null inherits the project webhook.'}, 'redirect_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Success redirect for this form only. Null inherits default_redirect_url.'}, 'notify_emails_override': {'anyOf': [{'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'maxItems': 10}, {'type': 'null'}], 'description': "Recipients for this form only. Null inherits the project's notify_emails."}, 'webhook_secret_override': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': "HMAC secret for this form's webhook. Null inherits the project secret."}, 'require_turnstile_override': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'description': 'Force Turnstile on or off for this form. Null inherits the project setting.'}}}, 'default': [], 'description': 'Forms to create or update on the site, matched by slug. Re-running is safe.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}, 'from_name': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Legacy From display name. Prefer email.from_name.'}, 'webhook_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Public https endpoint that receives a signed JSON POST per clean submission. Null disables it.'}, 'project_name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Website display name. Defaults to the client name.'}, 'project_slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Website slug. Defaults to the client slug.'}, 'notify_emails': {'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'default': [], 'maxItems': 10, 'description': 'Addresses that receive each clean submission (max 10). Inherited by every form.'}, 'honeypot_field': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Name of the hidden honeypot input the snippet emits. Defaults to _gotcha.'}, 'webhook_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'HMAC-SHA256 secret for the X-Furrow-Signature header. Omit to have one generated and returned.'}, 'allowed_domains': {'type': 'array', 'items': {'type': 'string', 'maxLength': 253, 'minLength': 1}, 'description': "Hostnames allowed to post to this project's forms (example.com, www.example.com). Empty accepts any origin."}, 'uploads_enabled': {'type': 'boolean', 'description': "Accept multipart file fields on this project's forms."}, 'turnstile_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile secret used to verify tokens server-side. Null removes it.'}, 'upload_max_files': {'type': 'integer', 'maximum': 20, 'description': 'Max files per submission (up to 20).', 'exclusiveMinimum': 0}, 'rate_limit_per_ip': {'type': 'integer', 'maximum': 1000, 'description': 'Max submissions per IP within rate_limit_window_s.', 'exclusiveMinimum': 0}, 'require_turnstile': {'type': 'boolean', 'description': 'Reject submissions that lack a valid Turnstile token.'}, 'turnstile_site_key': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile site key for the frontend widget. Null removes it.'}, 'rate_limit_window_s': {'type': 'integer', 'maximum': 86400, 'description': 'Rate-limit window in seconds (max 86400).', 'exclusiveMinimum': 0}, 'default_redirect_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Where classic HTML posts redirect after success. Null returns JSON/inline success instead.'}, 'upload_allowed_types': {'type': 'array', 'items': {'type': 'string', 'pattern': '^[a-z0-9.+-]+\\/[a-z0-9.+*-]+$', 'maxLength': 80, 'minLength': 3}, 'maxItems': 20, 'minItems': 1, 'description': 'Allowed upload MIME types, e.g. application/pdf or image/*.'}, 'upload_max_file_bytes': {'type': 'integer', 'maximum': 26214400, 'description': 'Max bytes per file (up to 25 MB).', 'exclusiveMinimum': 0}, 'upload_max_total_bytes': {'type': 'integer', 'maximum': 52428800, 'description': 'Max total upload bytes per submission (up to 50 MB).', 'exclusiveMinimum': 0}}}
create_client
Create client
Use to add a client (company) by slug. Idempotent on slug unless strict is true. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Prefer bootstrap_site when also creating a website and forms. Do not put Turnstile or webhooks on a client — those live on the project.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['name', 'slug'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Client (company) display name.'}, 'slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Lowercase hyphenated identifier, unique per team. Used for idempotent re-runs.'}, 'strict': {'type': 'boolean', 'default': False, 'description': 'When true, fail if the slug already exists instead of returning the existing record.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}}}
create_form
Create form
Use to add a form under an existing project_id. Idempotent on slug unless strict is true. Overrides are nullable; omit them to inherit project notify emails, webhook, redirect, require_turnstile, and email templates. Pass email.subject / email.intro to override notification copy for this form only. Pass webhook_url_override to send this form to a different URL. Field contract labels (label) are used on notification emails. Checkbox groups, select multiple, and other multi-value HTML fields must use a [] suffix on the name (name="services[]") or only the last checked value is stored. Put services[] on field_contract.name too. JSON/fetch should send a real array without brackets ({ "services": ["a", "b"] }) and must not use Object.fromEntries(formData) — use formData.getAll("services"). Single checkbox, select, or combobox: no brackets. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['name', 'slug', 'project_id'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Form display name, e.g. Contact or Quote request.'}, 'slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Lowercase hyphenated identifier, unique per project. Used for idempotent re-runs.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Per-form intro override. Supports {{tokens}}. Null falls back to the project intro.'}, 'subject': {'anyOf': [{'type': 'string', 'maxLength': 300, 'minLength': 1}, {'type': 'null'}], 'description': 'Per-form subject override. Supports {{tokens}}. Null falls back to the project subject.'}}, 'description': 'Per-form subject and intro overrides for the notification email.'}, 'strict': {'type': 'boolean', 'default': False, 'description': 'When true, fail if the slug already exists instead of returning the existing record.'}, 'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}, 'field_contract': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['name', 'type'], 'properties': {'name': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Posted field name. HTML checkbox groups / multi-select must end in [] (services[]) or only the last value is stored. JSON arrays omit the brackets.'}, 'type': {'type': 'string', 'maxLength': 40, 'minLength': 1, 'description': 'text, email, textarea, file, etc. type=file emits a file input in get_snippet.'}, 'label': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Human label used in the snippet and notification email. Defaults to the field name.'}, 'report': {'type': 'boolean', 'default': False, 'description': 'Include this field in dashboard summaries and reports.'}}}}, {'type': 'null'}], 'description': 'Declared fields (name, type, label, report). Drives get_snippet and the email layout. Null clears it.'}, 'webhook_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Webhook endpoint for this form only. Null inherits the project webhook.'}, 'redirect_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Success redirect for this form only. Null inherits default_redirect_url.'}, 'notify_emails_override': {'anyOf': [{'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'maxItems': 10}, {'type': 'null'}], 'description': "Recipients for this form only. Null inherits the project's notify_emails."}, 'webhook_secret_override': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': "HMAC secret for this form's webhook. Null inherits the project secret."}, 'require_turnstile_override': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'description': 'Force Turnstile on or off for this form. Null inherits the project setting.'}}}
create_project
Create project
Use to add one website under an existing client_id. Idempotent on slug unless strict is true. Free accounts are limited to 3 active (non-archived) projects; yearly is unlimited. If create_project returns plan_limit, call create_upgrade_link and hand the human the Stripe URL. Prefer bootstrap_site when the client does not exist yet. Optional email sets notification templates (subject, from_name, intro, footer_mode, include_meta). Free accounts cannot set footer_mode off. Set uploads_enabled on the project to accept files. Email templates support {{project.name}}, {{project.slug}}, {{form.name}}, {{form.slug}}, {{count}} (1-based clean submissions on this form, including the current one), and any submission field such as {{name}} or {{email}}. Use a fallback with {{name | "New submission"}}. Missing fields render empty and never error. Do not pass HTML; Furrow renders one owned layout. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet. Do not put Turnstile keys on a form.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['name', 'slug', 'client_id'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Project (website) display name.'}, 'slug': {'type': 'string', 'pattern': '^[a-z0-9]+(?:-[a-z0-9]+)*$', 'maxLength': 80, 'minLength': 1, 'description': 'Lowercase hyphenated identifier, unique per client. Used for idempotent re-runs.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Plain-text paragraph above the submitted fields. Supports {{tokens}}. Null removes it.'}, 'subject': {'type': 'string', 'maxLength': 300, 'minLength': 1, 'description': 'Notification email subject. Supports {{tokens}} such as {{form.name}} or {{name}}.'}, 'from_name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Display name on the From line of notification emails.'}, 'footer_mode': {'enum': ['furrow', 'minimal', 'off'], 'type': 'string', 'description': 'furrow shows the Furrow footer, minimal shows a one-line footer, off hides it (yearly plans only).'}, 'include_meta': {'type': 'boolean', 'description': 'Include submission metadata (IP, user agent, referrer, timestamp) below the fields.'}}, 'description': 'Notification email template for the project: subject, from_name, intro, footer_mode, include_meta.'}, 'strict': {'type': 'boolean', 'default': False, 'description': 'When true, fail if the slug already exists instead of returning the existing record.'}, 'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Client id from list_clients or create_client.'}, 'from_name': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Legacy From display name. Prefer email.from_name.'}, 'webhook_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Public https endpoint that receives a signed JSON POST per clean submission. Null disables it.'}, 'notify_emails': {'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'default': [], 'maxItems': 10, 'description': 'Addresses that receive each clean submission (max 10). Inherited by every form.'}, 'honeypot_field': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Name of the hidden honeypot input the snippet emits. Defaults to _gotcha.'}, 'webhook_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'HMAC-SHA256 secret for the X-Furrow-Signature header. Omit to have one generated and returned.'}, 'allowed_domains': {'type': 'array', 'items': {'type': 'string', 'maxLength': 253, 'minLength': 1}, 'description': "Hostnames allowed to post to this project's forms (example.com, www.example.com). Empty accepts any origin."}, 'uploads_enabled': {'type': 'boolean', 'description': "Accept multipart file fields on this project's forms."}, 'turnstile_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile secret used to verify tokens server-side. Null removes it.'}, 'upload_max_files': {'type': 'integer', 'maximum': 20, 'description': 'Max files per submission (up to 20).', 'exclusiveMinimum': 0}, 'rate_limit_per_ip': {'type': 'integer', 'maximum': 1000, 'description': 'Max submissions per IP within rate_limit_window_s.', 'exclusiveMinimum': 0}, 'require_turnstile': {'type': 'boolean', 'description': 'Reject submissions that lack a valid Turnstile token.'}, 'turnstile_site_key': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile site key for the frontend widget. Null removes it.'}, 'rate_limit_window_s': {'type': 'integer', 'maximum': 86400, 'description': 'Rate-limit window in seconds (max 86400).', 'exclusiveMinimum': 0}, 'default_redirect_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Where classic HTML posts redirect after success. Null returns JSON/inline success instead.'}, 'upload_allowed_types': {'type': 'array', 'items': {'type': 'string', 'pattern': '^[a-z0-9.+-]+\\/[a-z0-9.+*-]+$', 'maxLength': 80, 'minLength': 3}, 'maxItems': 20, 'minItems': 1, 'description': 'Allowed upload MIME types, e.g. application/pdf or image/*.'}, 'upload_max_file_bytes': {'type': 'integer', 'maximum': 26214400, 'description': 'Max bytes per file (up to 25 MB).', 'exclusiveMinimum': 0}, 'upload_max_total_bytes': {'type': 'integer', 'maximum': 52428800, 'description': 'Max total upload bytes per submission (up to 50 MB).', 'exclusiveMinimum': 0}}}
create_upgrade_link
Create upgrade link
Use when a free team is blocked on the 3-project, 100-submission, or one-workspace limit, or when a human should upgrade to yearly ($199/year) so you can keep adding projects, workspaces, and storing new submissions. Returns a Stripe Checkout URL to hand the human. If the team is already yearly, omit product to get a Stripe Customer Portal URL, or pass product=expansion for a $99/year pack that adds 5,000 submissions/month. Do not change plan in the database — Stripe webhooks do that. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace.
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'product': {'enum': ['yearly', 'expansion'], 'type': 'string', 'description': 'yearly starts the $199/year plan; expansion adds a $99/year 5,000-submission pack to a yearly team.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}}}
get_account
Get account
Use to read the team's plan, usage this month, limits, expansion_packs, retention_days, branding, and AI spam filter (ai.typesafe_key_set, ai.notify_suspected_spam). The TypeSafe key itself is never returned. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use this to list projects — call list_projects.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}}}
get_client
Get client
Use to inspect one client. Do not use to list that client's sites — call list_projects with client_id.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['client_id'], 'properties': {'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Client id from list_clients or create_client.'}}}
get_form
Get form
Use to read a form plus resolved and inherited settings, including resolved.email and resolved.uploads. files_url is the private inbox folder for this form under the project inbox (do not put it in a public snippet). Turnstile always inherits from the project (inherited.turnstile is true). Form webhook_url_override is nullable; null means inherit the project webhook. Forms may override email.subject and email.intro only. Do not use to generate embed code — call get_snippet.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['form_id'], 'properties': {'form_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Form id from list_forms, create_form, or bootstrap_site.'}}}
get_project
Get project
Use to inspect one website's settings, domains, webhook URL, email templates, upload policy, plan, usage this month, limits, retention_days, and branding. Returns stored values plus resolved.email. Secrets are never returned. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. Do not use to list forms — call list_forms.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['project_id'], 'properties': {'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}}}
get_snippet
Get form snippet
Use after the form exists to get copy-paste html, astro, or next code that posts to /s/:publicKey. Do not invent a submit URL — this tool returns it. Snippets are single-value inputs; write checkbox groups and multi-selects yourself. Checkbox groups, select multiple, and other multi-value HTML fields must use a [] suffix on the name (name="services[]") or only the last checked value is stored. Put services[] on field_contract.name too. JSON/fetch should send a real array without brackets ({ "services": ["a", "b"] }) and must not use Object.fromEntries(formData) — use formData.getAll("services"). Single checkbox, select, or combobox: no brackets. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet. After dropping the snippet, add visitor-facing success UI: a thank-you page plus default_redirect_url for classic HTML POST, or an on-page message when using fetch/{ok:true}. Do not leave visitors looking at JSON. See furrow://docs/submit-success.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['form_id', 'framework'], 'properties': {'form_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Form id from list_forms, create_form, or bootstrap_site.'}, 'framework': {'enum': ['html', 'astro', 'next'], 'type': 'string', 'description': 'Snippet flavor: html (plain form), astro (component), or next (React client component).'}}}
get_submission
Get submission
Use to read one stored submission by id, including file metadata and files_url (the form inbox). Do not use this for live submit tests — POST to /s/:publicKey instead. Do not expect signed bucket URLs — download from the inbox.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['submission_id'], 'properties': {'submission_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Submission id from list_submissions or a webhook payload.'}}}
list_clients
List clients
Use to browse clients (companies). Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use to list websites — call list_projects with client_id. Archived clients are hidden unless status=all or archived.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'description': 'Page size, max 100.', 'exclusiveMinimum': 0}, 'cursor': {'type': 'string', 'minLength': 1, 'description': "Opaque cursor from the previous page's next_cursor."}, 'status': {'enum': ['active', 'archived', 'all'], 'type': 'string', 'description': 'Filter by archive state. Defaults to active.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}}}
list_forms
List forms
Use to list forms. Pass project_id for one website or client_id for one company (team is inferred). Otherwise Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use to read resolved inheritance — call get_form.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'description': 'Page size, max 100.', 'exclusiveMinimum': 0}, 'cursor': {'type': 'string', 'minLength': 1, 'description': "Opaque cursor from the previous page's next_cursor."}, 'status': {'enum': ['active', 'archived', 'all'], 'type': 'string', 'description': 'Filter by archive state. Defaults to active.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}, 'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': "Only forms under this client's projects."}, 'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Only forms in this project.'}}}
list_projects
List projects
Use to browse websites. Pass client_id to list one client's sites (team is inferred). Without client_id, Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use to read one project's settings — call get_project. Archived projects are hidden unless status=all or archived.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'limit': {'type': 'integer', 'maximum': 100, 'description': 'Page size, max 100.', 'exclusiveMinimum': 0}, 'cursor': {'type': 'string', 'minLength': 1, 'description': "Opaque cursor from the previous page's next_cursor."}, 'status': {'enum': ['active', 'archived', 'all'], 'type': 'string', 'description': 'Filter by archive state. Defaults to active.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}, 'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Only projects under this client.'}}}
list_submissions
List submissions
Use to inspect recent posts. Available on free and yearly. Pass project_id or form_id to infer the team; omit both and Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Free teams prune submissions after 30 days; yearly after 730 days. Do not use to read a single submission — call get_submission.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'spam': {'enum': ['clean', 'spam', 'unknown', 'all'], 'type': 'string', 'default': 'clean', 'description': 'Which submissions to return. Defaults to clean.'}, 'limit': {'type': 'integer', 'maximum': 100, 'description': 'Page size, max 100.', 'exclusiveMinimum': 0}, 'since': {'type': 'string', 'minLength': 1, 'description': 'ISO 8601 timestamp; only submissions created after this instant.'}, 'cursor': {'type': 'string', 'minLength': 1, 'description': "Opaque cursor from the previous page's next_cursor."}, 'form_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Only submissions to this form.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}, 'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Only submissions in this project.'}}}
list_teams
List teams
Use to see workspaces this token can act on. A team-scoped frw_ token returns that team only. An operator token or an MCP OAuth login returns every team you belong to — pass team_id on list_clients, create_client, and bootstrap_site. Creating another workspace is dashboard-only after signing in and requires a yearly plan; free accounts stay on one team. Do not use this to list clients — call list_clients.
읽기 전용 멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {}}
rotate_project_inbox
Rotate project inbox link
Use when a project's private inbox URL may have leaked. Issues a new inbox key; every previously shared inbox link stops working immediately. get_project returns the new files_url.
파괴적 작업
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['project_id'], 'properties': {'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}}}
test_webhook
Test webhook
Use to POST a signed webhook.test event to the project's webhook URL. Available on free and yearly. Do not use this to inspect past deliveries — that is REST GET /api/projects/:id/webhook-deliveries. Fails if no webhook URL and secret are set.
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['project_id'], 'properties': {'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}}}
update_account
Update account
Use to rename the workspace, set branding, or configure the AI spam filter. Pass ai.typesafe_api_key to save this team's TypeSafe key (null clears it; the key is write-only). ai.notify_suspected_spam (default true) emails notify addresses with a Potential spam label when Jev flags a submission; no webhook is sent either way. Heuristic spam stays silent. Free accounts cannot set branding.mode or email.footer_mode to anything but furrow — those writes are ignored. Yearly may set branding.mode to furrow, off, or agency (name + optional link + optional logo; Furrow keeps the layout, no raw HTML). Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use this to change the plan — call create_upgrade_link.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'properties': {'ai': {'type': 'object', 'properties': {'typesafe_api_key': {'anyOf': [{'type': 'string', 'maxLength': 500}, {'type': 'null'}], 'description': 'TypeSafe API key for the AI spam filter. Send null or an empty string to remove it. The key is write-only and never returned.'}, 'notify_suspected_spam': {'type': 'boolean', 'description': 'When true (the default), submissions the AI flags as spam still email notify addresses with a Potential spam label and send no webhook. Set false to silence those emails.'}}, 'description': 'AI spam filter. Off until this team saves its own TypeSafe key. One Jev call per heuristics-clean submission.'}, 'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'New workspace display name.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}, 'branding': {'type': 'object', 'properties': {'mode': {'enum': ['furrow', 'off', 'agency'], 'type': 'string', 'description': 'furrow shows Furrow branding, off hides it, agency shows your own name/link/logo. Only yearly plans may change this.'}, 'logo_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Optional http(s) logo image URL when mode is agency. Null clears it.'}, 'agency_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Optional http(s) link for the agency name when mode is agency. Null clears it.'}, 'agency_name': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Agency name shown when mode is agency. Null clears it.'}}, 'description': 'Account-wide branding for notification emails and hosted pages.'}}}
update_client
Update client
Use to rename a client. Do not use this to change domains, Turnstile, or webhooks — call update_project.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['client_id'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'New client display name.'}, 'client_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Client id from list_clients or create_client.'}}}
update_form
Update form
Use to patch form overrides. Pass null on an override to inherit again. Set email.subject or email.intro to override project notification copy for this form only; from_name, footer_mode, and include_meta stay on the project. Email templates support {{project.name}}, {{project.slug}}, {{form.name}}, {{form.slug}}, {{count}} (1-based clean submissions on this form, including the current one), and any submission field such as {{name}} or {{email}}. Use a fallback with {{name | "New submission"}}. Missing fields render empty and never error. Do not pass HTML; Furrow renders one owned layout. Set webhook_url_override to override the project webhook for this form. Do not use this to change Turnstile keys — those live on the project. Checkbox groups, select multiple, and other multi-value HTML fields must use a [] suffix on the name (name="services[]") or only the last checked value is stored. Put services[] on field_contract.name too. JSON/fetch should send a real array without brackets ({ "services": ["a", "b"] }) and must not use Object.fromEntries(formData) — use formData.getAll("services"). Single checkbox, select, or combobox: no brackets. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['form_id'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'New form display name.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Per-form intro override. Supports {{tokens}}. Null falls back to the project intro.'}, 'subject': {'anyOf': [{'type': 'string', 'maxLength': 300, 'minLength': 1}, {'type': 'null'}], 'description': 'Per-form subject override. Supports {{tokens}}. Null falls back to the project subject.'}}, 'description': 'Per-form subject and intro overrides for the notification email.'}, 'form_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Form id from list_forms, create_form, or bootstrap_site.'}, 'field_contract': {'anyOf': [{'type': 'array', 'items': {'type': 'object', 'required': ['name', 'type'], 'properties': {'name': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Posted field name. HTML checkbox groups / multi-select must end in [] (services[]) or only the last value is stored. JSON arrays omit the brackets.'}, 'type': {'type': 'string', 'maxLength': 40, 'minLength': 1, 'description': 'text, email, textarea, file, etc. type=file emits a file input in get_snippet.'}, 'label': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Human label used in the snippet and notification email. Defaults to the field name.'}, 'report': {'type': 'boolean', 'default': False, 'description': 'Include this field in dashboard summaries and reports.'}}}}, {'type': 'null'}], 'description': 'Declared fields (name, type, label, report). Drives get_snippet and the email layout. Null clears it.'}, 'webhook_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Webhook endpoint for this form only. Null inherits the project webhook.'}, 'redirect_url_override': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Success redirect for this form only. Null inherits default_redirect_url.'}, 'notify_emails_override': {'anyOf': [{'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'maxItems': 10}, {'type': 'null'}], 'description': "Recipients for this form only. Null inherits the project's notify_emails."}, 'webhook_secret_override': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': "HMAC secret for this form's webhook. Null inherits the project secret."}, 'require_turnstile_override': {'anyOf': [{'type': 'boolean'}, {'type': 'null'}], 'description': 'Force Turnstile on or off for this form. Null inherits the project setting.'}}}
update_project
Update project
Use to patch website settings. Nested email sets notification templates: email.subject, email.from_name, email.intro, email.footer_mode (furrow | minimal | off), email.include_meta. Optional branding updates the team (mode furrow | off | agency on yearly only; ignored on free except furrow). Set uploads_enabled and upload_allowed_types here — forms inherit them. Email templates support {{project.name}}, {{project.slug}}, {{form.name}}, {{form.slug}}, {{count}} (1-based clean submissions on this form, including the current one), and any submission field such as {{name}} or {{email}}. Use a fallback with {{name | "New submission"}}. Missing fields render empty and never error. Do not pass HTML; Furrow renders one owned layout. Plans are capacity, not a feature ladder. Free: one workspace, 100 clean submissions/month pooled on the team, 3 active (non-archived) projects, 30-day submission retention, and email.footer_mode / branding.mode stay furrow. Yearly ($199/year): extra workspaces, 10,000 submissions/month, unlimited projects, 730-day retention, branding.mode furrow | off | agency. Expansion packs are $99/year each and add 5,000 submissions/month on a yearly account. MCP, webhooks, snippets, inheritance, Turnstile, and email templates stay available on free. Spam, honeypot, and rejected posts do not count. Use create_upgrade_link to hand a human a Stripe Checkout (or Customer Portal) URL. Pass product=expansion on a yearly team to add a pack. File inputs use multipart POST (name="resume[]" for multiple). Enable uploads on the project first. Downloads are the project inbox at files_url, with the form slug as a folder — never put that URL in a public snippet. Null Turnstile or webhook fields clear them. Do not use this to create forms. Updating project keys changes resolved settings on existing forms unless a form override is set.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['project_id'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'New project display name.'}, 'email': {'type': 'object', 'properties': {'intro': {'anyOf': [{'type': 'string', 'maxLength': 4000}, {'type': 'null'}], 'description': 'Plain-text paragraph above the submitted fields. Supports {{tokens}}. Null removes it.'}, 'subject': {'type': 'string', 'maxLength': 300, 'minLength': 1, 'description': 'Notification email subject. Supports {{tokens}} such as {{form.name}} or {{name}}.'}, 'from_name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'Display name on the From line of notification emails.'}, 'footer_mode': {'enum': ['furrow', 'minimal', 'off'], 'type': 'string', 'description': 'furrow shows the Furrow footer, minimal shows a one-line footer, off hides it (yearly plans only).'}, 'include_meta': {'type': 'boolean', 'description': 'Include submission metadata (IP, user agent, referrer, timestamp) below the fields.'}}, 'description': 'Notification email template for the project: subject, from_name, intro, footer_mode, include_meta.'}, 'branding': {'type': 'object', 'properties': {'mode': {'enum': ['furrow', 'off', 'agency'], 'type': 'string', 'description': 'furrow shows Furrow branding, off hides it, agency shows your own name/link/logo. Only yearly plans may change this.'}, 'logo_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Optional http(s) logo image URL when mode is agency. Null clears it.'}, 'agency_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Optional http(s) link for the agency name when mode is agency. Null clears it.'}, 'agency_name': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Agency name shown when mode is agency. Null clears it.'}}, 'description': 'Per-project branding override for notification emails and hosted pages.'}, 'from_name': {'anyOf': [{'type': 'string', 'maxLength': 120, 'minLength': 1}, {'type': 'null'}], 'description': 'Legacy From display name. Prefer email.from_name.'}, 'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}, 'webhook_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Public https endpoint that receives a signed JSON POST per clean submission. Null disables it.'}, 'notify_emails': {'type': 'array', 'items': {'type': 'string', 'format': 'email', 'pattern': "^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"}, 'maxItems': 10, 'description': 'Replace the notification recipients (max 10).'}, 'honeypot_field': {'type': 'string', 'maxLength': 80, 'minLength': 1, 'description': 'Name of the hidden honeypot input the snippet emits. Defaults to _gotcha.'}, 'webhook_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'HMAC-SHA256 secret for the X-Furrow-Signature header. Omit to have one generated and returned.'}, 'allowed_domains': {'type': 'array', 'items': {'type': 'string', 'maxLength': 253, 'minLength': 1}, 'description': 'Replace the allowed hostnames. Empty array accepts any origin.'}, 'uploads_enabled': {'type': 'boolean', 'description': "Accept multipart file fields on this project's forms."}, 'turnstile_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile secret used to verify tokens server-side. Null removes it.'}, 'upload_max_files': {'type': 'integer', 'maximum': 20, 'description': 'Max files per submission (up to 20).', 'exclusiveMinimum': 0}, 'rate_limit_per_ip': {'type': 'integer', 'maximum': 1000, 'description': 'Max submissions per IP within rate_limit_window_s.', 'exclusiveMinimum': 0}, 'require_turnstile': {'type': 'boolean', 'description': 'Reject submissions that lack a valid Turnstile token.'}, 'turnstile_site_key': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'Cloudflare Turnstile site key for the frontend widget. Null removes it.'}, 'rate_limit_window_s': {'type': 'integer', 'maximum': 86400, 'description': 'Rate-limit window in seconds (max 86400).', 'exclusiveMinimum': 0}, 'default_redirect_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Where classic HTML posts redirect after success. Null returns JSON/inline success instead.'}, 'upload_allowed_types': {'type': 'array', 'items': {'type': 'string', 'pattern': '^[a-z0-9.+-]+\\/[a-z0-9.+*-]+$', 'maxLength': 80, 'minLength': 3}, 'maxItems': 20, 'minItems': 1, 'description': 'Allowed upload MIME types, e.g. application/pdf or image/*.'}, 'upload_max_file_bytes': {'type': 'integer', 'maximum': 26214400, 'description': 'Max bytes per file (up to 25 MB).', 'exclusiveMinimum': 0}, 'upload_max_total_bytes': {'type': 'integer', 'maximum': 52428800, 'description': 'Max total upload bytes per submission (up to 50 MB).', 'exclusiveMinimum': 0}}}
update_team
Update team
Use to rename a team. Operator tokens (all workspaces) must pass team_id from list_teams. Team-scoped tokens may omit team_id and cannot target another workspace. Do not use this to create another workspace — that is dashboard-only and requires yearly. For plan, usage, retention, and branding, call get_account / update_account. Do not use this to change clients or projects.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['name'], 'properties': {'name': {'type': 'string', 'maxLength': 120, 'minLength': 1, 'description': 'New team (workspace) display name.'}, 'team_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Team id from list_teams. Required for operator tokens and OAuth logins; team-scoped frw_ tokens may omit it.'}}}
upsert_project_webhook
Upsert project webhook
Use to set or replace the project webhook URL. Available on free and yearly. A signing secret is generated once if the project has none. Forms inherit this unless webhook_url_override is set. Do not use this for a one-off delivery — call test_webhook.
멱등성
입력 스키마
{'type': 'object', '$schema': 'https://json-schema.org/draft/2020-12/schema', 'required': ['webhook_url', 'project_id'], 'properties': {'project_id': {'type': 'string', 'format': 'uuid', 'pattern': '^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$', 'description': 'Project (website) id from list_projects, create_project, or bootstrap_site.'}, 'webhook_url': {'anyOf': [{'type': 'string', 'format': 'uri'}, {'type': 'null'}], 'description': 'Public https endpoint that receives a signed JSON POST per clean submission. Null disables it.'}, 'webhook_secret': {'anyOf': [{'type': 'string', 'minLength': 1}, {'type': 'null'}], 'description': 'HMAC-SHA256 secret for X-Furrow-Signature. Omit to have one generated and returned.'}}}
변경됨
update_account
2026년 9월 23일 2:53 AM
변경됨
get_account
2026년 9월 23일 2:53 AM
추가됨
bootstrap_site
2026년 9월 17일 12:34 PM
추가됨
test_webhook
2026년 9월 17일 12:34 PM
추가됨
upsert_project_webhook
2026년 9월 17일 12:34 PM
추가됨
archive_form
2026년 9월 17일 12:34 PM
추가됨
update_form
2026년 9월 17일 12:34 PM
추가됨
create_form
2026년 9월 17일 12:34 PM
추가됨
archive_project
2026년 9월 17일 12:34 PM
추가됨
rotate_project_inbox
2026년 9월 17일 12:34 PM
추가됨
update_project
2026년 9월 17일 12:34 PM
추가됨
create_project
2026년 9월 17일 12:34 PM
추가됨
get_snippet
2026년 9월 17일 12:34 PM
추가됨
get_submission
2026년 9월 17일 12:34 PM
추가됨
list_submissions
2026년 9월 17일 12:34 PM
추가됨
get_form
2026년 9월 17일 12:34 PM
추가됨
list_forms
2026년 9월 17일 12:34 PM
추가됨
get_project
2026년 9월 17일 12:34 PM
추가됨
list_projects
2026년 9월 17일 12:34 PM
추가됨
archive_client
2026년 9월 17일 12:34 PM
추가됨
update_client
2026년 9월 17일 12:34 PM
추가됨
create_client
2026년 9월 17일 12:34 PM
추가됨
get_client
2026년 9월 17일 12:34 PM
추가됨
list_clients
2026년 9월 17일 12:34 PM
추가됨
create_upgrade_link
2026년 9월 17일 12:34 PM
추가됨
update_account
2026년 9월 17일 12:34 PM
추가됨
get_account
2026년 9월 17일 12:34 PM
추가됨
update_team
2026년 9월 17일 12:34 PM
추가됨
list_teams
2026년 9월 17일 12:34 PM