{"components":{"responses":{"Error":{"content":{"application/json":{"schema":{"properties":{"error":{"type":"string"}},"type":"object"}}},"description":"Error code, e.g. unauthorized, not_found, website_required, too_many_websites, too_many_recipients, invalid_kind, too_many_integrations, invalid_url, invalid_telegram, rate_limited, invalid_spam_filter, invalid_blocked_ips, invalid_email_template, invalid_cursor, body_too_large (JSON body over 1 MB, status 413), read_only_key (the key may only read) or key_scope (the key is limited to another website)"},"RateLimited":{"content":{"application/json":{"schema":{"properties":{"error":{"const":"rate_limited"}},"type":"object"}}},"description":"More than 120 requests per minute with one API key","headers":{"Retry-After":{"schema":{"type":"integer"}}}}},"schemas":{"Delivery":{"properties":{"attempts":{"type":"integer"},"can_retry":{"description":"True while the payload is still stored (7 days)","type":"boolean"},"created_at":{"type":"integer"},"error":{"description":"Error code such as EBLOCKED or http_500; empty on success","type":"string"},"http_status":{"type":["integer","null"]},"id":{"type":"string"},"integration_id":{"type":"string"},"next_attempt_at":{"type":"integer"},"status":{"enum":["failed","ok","pending"],"type":"string"}},"type":"object"},"Form":{"properties":{"autoresponder_subject":{"type":"string"},"email_html":{"description":"Used with email_template custom. Placeholders: {fields}, {intro}, {form}, {field:name}","type":"string"},"email_template":{"enum":["default","plain","minimal","modern","compact","dark","custom"],"type":"string"},"intro_text":{"type":"string"},"allowed_domains":{"type":"string"},"autoresponder_text":{"type":"string"},"blocked_ips":{"description":"IPs or CIDR ranges (IPv4 and IPv6), separated by spaces, commas or new lines. Submissions from them are dropped silently.","type":"string"},"captcha":{"description":"Captcha checked on submit; \"\" = none. Forms inherit the website setting.","enum":["","altcha","hcaptcha","turnstile","recaptcha"],"type":"string"},"captcha_secret_set":{"type":"boolean"},"created_at":{"description":"Unix time in ms","type":"integer"},"delivered":{"description":"Only in lists","type":"integer"},"disabled":{"enum":[0,1],"type":"integer"},"from_name":{"type":"string"},"group_emails":{"enum":[0,1],"type":"integer"},"id":{"description":"Also the public access key","type":"string"},"name":{"type":"string"},"recipients":{"items":{"format":"email","type":"string"},"type":"array"},"redirect_url":{"type":"string"},"require_consent":{"enum":[0,1],"type":"integer"},"retention_days":{"enum":[0,7,30,90,180,365],"type":"integer"},"server_requires_key":{"enum":[0,1],"type":"integer"},"spam_filter":{"description":"\"\" = off, basic = link dumps and sales pitches, strict = any link or HTML. Caught submissions go to the spam tab for 7 days.","enum":["","basic","strict"],"type":"string"},"subject":{"type":"string"},"submissions":{"type":"integer"},"website":{"type":"string"},"website_id":{"type":"string"}},"type":"object"},"FormPatch":{"properties":{"autoresponder_subject":{"type":"string"},"email_html":{"description":"Used with email_template custom. Placeholders: {fields}, {intro}, {form}, {field:name}","type":"string"},"email_template":{"enum":["default","plain","minimal","modern","compact","dark","custom"],"type":"string"},"intro_text":{"type":"string"},"allowed_domains":{"type":"string"},"autoresponder_text":{"type":"string"},"blocked_ips":{"description":"IPs or CIDR ranges (IPv4 and IPv6), separated by spaces, commas or new lines. Submissions from them are dropped silently.","type":"string"},"captcha":{"description":"Captcha checked on submit; \"\" = none. Forms inherit the website setting.","enum":["","altcha","hcaptcha","turnstile","recaptcha"],"type":"string"},"captcha_secret":{"description":"Secret key for hcaptcha, turnstile or recaptcha. Write-only, stored encrypted; \"\" clears it.","type":"string","writeOnly":true},"disabled":{"type":"boolean"},"from_name":{"type":"string"},"group_emails":{"description":"true: mails of a form share a thread in the inbox. false: each subject gets a short submission id.","type":"boolean"},"name":{"type":"string"},"recipients":{"description":"At most as many as the plan allows per form","items":{"format":"email","type":"string"},"type":"array"},"redirect_url":{"type":"string"},"require_consent":{"type":"boolean"},"retention_days":{"enum":[0,7,30,90,180,365],"type":"integer"},"server_requires_key":{"description":"true: submissions without Origin or Referer need the owner's API key as Bearer token, otherwise 403 api_key_required.","type":"boolean"},"spam_filter":{"description":"\"\" = off, basic = link dumps and sales pitches, strict = any link or HTML. Caught submissions go to the spam tab for 7 days.","enum":["","basic","strict"],"type":"string"},"subject":{"type":"string"},"website":{"type":"string"}},"type":"object"},"Integration":{"properties":{"created_at":{"type":"integer"},"disabled":{"type":"boolean"},"form_id":{"description":"Null: every form of the website","type":["string","null"]},"hint":{"description":"Masked destination; the full URL or bot token is never returned","type":"string"},"id":{"type":"string"},"kind":{"enum":["webhook","slack","discord","telegram"],"type":"string"},"label":{"type":"string"},"secret":{"description":"Webhooks only: HMAC-SHA256 key for the Mailiy-Signature header","type":["string","null"]},"website_id":{"type":"string"}},"type":"object"},"Ok":{"properties":{"ok":{"const":true}},"type":"object"},"Submission":{"properties":{"consent_at":{"type":["integer","null"]},"created_at":{"type":"integer"},"data":{"description":"Field name/value pairs in submitted order","items":{"items":{"type":"string"},"maxItems":2,"minItems":2,"type":"array"},"type":"array"},"files":{"items":{"properties":{"field":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"size":{"description":"Bytes","type":"integer"},"type":{"type":"string"},"url":{"description":"Signed download link, valid for one hour","type":"string"}},"type":"object"},"type":"array"},"form_id":{"type":"string"},"id":{"type":"string"},"spam":{"description":"In the spam tab","type":"boolean"}},"type":"object"},"Website":{"description":"Groups forms. Its settings are the defaults for forms created later.","properties":{"captcha":{"description":"Captcha checked on submit; \"\" = none. Forms inherit the website setting.","enum":["","altcha","hcaptcha","turnstile","recaptcha"],"type":"string"},"captcha_secret_set":{"type":"boolean"},"created_at":{"description":"Unix time in ms","type":"integer"},"delivered":{"description":"Delivered submissions counted so far; only in lists","type":"integer"},"domains":{"description":"Comma-separated domains allowed to submit","type":"string"},"forms":{"description":"Only in lists","type":"integer"},"from_name":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"recipients":{"description":"At most as many as the plan allows per form","items":{"format":"email","type":"string"},"type":"array"},"retention_days":{"enum":[0,7,30,90,180,365],"type":"integer"},"spam":{"description":"Blocked spam counted so far; only in lists","type":"integer"}},"type":"object"},"WebsitePatch":{"properties":{"captcha":{"description":"Captcha checked on submit; \"\" = none. Forms inherit the website setting.","enum":["","altcha","hcaptcha","turnstile","recaptcha"],"type":"string"},"captcha_secret":{"description":"Secret key for hcaptcha, turnstile or recaptcha. Write-only, stored encrypted; \"\" clears it.","type":"string","writeOnly":true},"domains":{"type":"string"},"from_name":{"type":"string"},"name":{"type":"string"},"recipients":{"description":"At most as many as the plan allows per form","items":{"format":"email","type":"string"},"type":"array"},"retention_days":{"enum":[0,7,30,90,180,365],"type":"integer"}},"type":"object"}},"securitySchemes":{"bearerAuth":{"description":"Personal API key (mk_…) from Dashboard → Account → API keys","scheme":"bearer","type":"http"}}},"info":{"description":"EU-hosted, GDPR-compliant form-to-email service. /api/submit is public (the form id is the access key); everything else needs an API key. A key can be read-only (GET only) or limited to one website; then lists only show that website and other ids answer 404. Each key may send 120 requests per minute; every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds).","title":"Mailiy API","version":"1.0.0"},"openapi":"3.1.0","paths":{"/api/altcha":{"get":{"description":"Proof-of-work challenge for the ALTCHA widget (challengeurl). 30 per minute per client.","operationId":"altchaChallenge","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"parameters":{"type":"object"},"signature":{"type":"string"}},"type":"object"}}},"description":"Challenge to solve in the browser"},"429":{"description":"rate_limited"}},"security":[]}},"/api/deliveries/{id}/retry":{"post":{"description":"Sends a failed or pending delivery again now. Possible while can_retry is true.","operationId":"retryDelivery","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"The delivery"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/files/{id}":{"delete":{"description":"Deletes one uploaded file and frees storage; the submission stays.","operationId":"deleteFile","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"description":"Downloads an uploaded file. The link comes signed in the notification email and webhook (valid 30 days) or in listSubmissions (valid one hour) and stops working once the submission is deleted.","operationId":"downloadFile","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"expires","required":true,"schema":{"type":"integer"}},{"in":"query","name":"signature","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/octet-stream":{}},"description":"The file as attachment"},"404":{"description":"Unknown, expired, deleted or wrongly signed"}},"security":[]}},"/api/forms":{"get":{"operationId":"listForms","parameters":[{"description":"Only forms of this website","in":"query","name":"website","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Form"},"type":"array"}}},"description":"All forms"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createForm","requestBody":{"content":{"application/json":{"schema":{"properties":{"name":{"type":"string"},"website":{"type":"string"},"website_id":{"description":"Optional when the account has exactly one website, or the API key is limited to exactly one. Otherwise 400 website_required.","type":"string"}},"required":["name"],"type":"object"}}}},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"id":{"type":"string"},"website_id":{"type":"string"}},"type":"object"}}},"description":"Created with the website's defaults"},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}":{"delete":{"operationId":"deleteForm","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"operationId":"getForm","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}},"description":"The form"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateForm","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormPatch"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}},"description":"Updated form"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/duplicate":{"post":{"description":"Copies the form with all settings under a new access key. The copy has no submissions.","operationId":"duplicateForm","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"name":{"description":"Default: the old name plus \" (copy)\"","type":"string"}},"type":"object"}}}},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"id":{"type":"string"},"name":{"type":"string"}},"type":"object"}}},"description":"The copy"},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/preview":{"post":{"description":"Renders the notification email and auto-reply with sample data. Fields in the body override the saved settings without storing them.","operationId":"previewEmail","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormPatch"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"html":{"type":"string"},"subject":{"type":"string"},"text":{"type":"string"},"autoresponder":{"oneOf":[{"properties":{"html":{"type":"string"},"subject":{"type":"string"},"text":{"type":"string"}},"type":"object"},{"type":"null"}]}},"type":"object"}}},"description":"Rendered emails"},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/rotate-key":{"post":{"description":"Gives the form a new id and with it a new access key. The old key stops working at once; submissions move to the new id.","operationId":"rotateAccessKey","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"id":{"type":"string"}},"type":"object"}}},"description":"The new id and access key"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/submissions":{"get":{"operationId":"listSubmissions","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"true lists the spam tab instead","in":"query","name":"spam","schema":{"type":"boolean"}},{"description":"Rows per page, newest first","in":"query","name":"limit","schema":{"default":500,"maximum":500,"minimum":1,"type":"integer"}},{"description":"X-Next-Cursor from the previous page","in":"query","name":"cursor","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Submission"},"type":"array"}}},"description":"Stored submissions (empty when retention is 0). X-Next-Cursor is set when older rows exist.","headers":{"X-Next-Cursor":{"schema":{"type":"string"}}}},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/submissions.csv":{"get":{"operationId":"exportSubmissions","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"description":"Comma-separated field names for the columns; default: all fields","in":"query","name":"fields","schema":{"type":"string"}}],"responses":{"200":{"content":{"text/csv":{}},"description":"CSV export; a files column with 30-day download links appears when submissions have files"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/submissions/{sid}":{"delete":{"operationId":"deleteSubmission","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"sid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/submissions/{sid}/not-spam":{"post":{"description":"Delivers a submission from the spam tab and moves it to the normal list.","operationId":"markNotSpam","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"path","name":"sid","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/forms/{id}/test":{"post":{"operationId":"sendTest","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/integrations/{id}":{"delete":{"operationId":"deleteIntegration","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"operationId":"updateIntegration","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"disabled":{"type":"boolean"},"label":{"maxLength":80,"type":"string"}},"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Integration"}}},"description":"Updated integration"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/integrations/{id}/deliveries":{"get":{"description":"The newest 50 deliveries. The log is kept for 30 days.","operationId":"listDeliveries","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Delivery"},"type":"array"}}},"description":"Deliveries, newest first"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/integrations/{id}/test":{"post":{"description":"Sends a sample submission once. A failed test is not retried on its own.","operationId":"testIntegration","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"The delivery"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/stats":{"get":{"description":"Delivered and spam counts per day (7d, 30d) or month (12m), oldest first, gaps filled with 0.","operationId":"getStats","parameters":[{"in":"query","name":"range","schema":{"default":"30d","enum":["7d","30d","12m"],"type":"string"}},{"description":"Only this website","in":"query","name":"website","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"points":{"items":{"properties":{"date":{"description":"YYYY-MM-DD, or YYYY-MM for 12m","type":"string"},"delivered":{"type":"integer"},"spam":{"type":"integer"}},"type":"object"},"type":"array"},"range":{"type":"string"}},"type":"object"}}},"description":"Counts per day or month"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/status":{"get":{"description":"Public service health, cached for 30 seconds. backup only appears where backups are set up.","operationId":"getStatus","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"checked_at":{"description":"Unix time in milliseconds","type":"integer"},"checks":{"additionalProperties":{"type":"boolean"},"properties":{"backup":{"type":"boolean"},"database":{"type":"boolean"},"jobs":{"type":"boolean"},"mail":{"type":"boolean"}},"type":"object"},"status":{"enum":["ok","degraded"],"type":"string"}},"type":"object"}}},"description":"ok when every check passes, otherwise degraded"}},"security":[]}},"/api/submissions":{"get":{"description":"Submissions of all forms in the account, newest first, 25 per page, in blocks of 1000 (see cursor). q searches field names and values.","operationId":"searchSubmissions","parameters":[{"description":"Only this website","in":"query","name":"website","schema":{"type":"string"}},{"in":"query","name":"form","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"enum":["delivered","spam"],"type":"string"}},{"description":"Search term","in":"query","name":"q","schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"description":"next_cursor of the previous result. Opens the next block of 1000 older submissions; page and q work inside the block. A broken cursor fails with 400 invalid_cursor.","in":"query","name":"cursor","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"keys":{"description":"Field names found in the matches","items":{"type":"string"},"type":"array"},"next_cursor":{"description":"Set when older submissions exist beyond this block","type":["string","null"]},"page":{"type":"integer"},"pages":{"type":"integer"},"rows":{"items":{"allOf":[{"$ref":"#/components/schemas/Submission"},{"properties":{"form_name":{"type":"string"}},"type":"object"}]},"type":"array"},"total":{"type":"integer"}},"type":"object"}}},"description":"One page of matches"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/submissions.csv":{"get":{"description":"CSV of all matches with the same filters, plus a form column.","operationId":"exportSubmissions","parameters":[{"description":"Only this website","in":"query","name":"website","schema":{"type":"string"}},{"in":"query","name":"form","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"enum":["delivered","spam"],"type":"string"}},{"description":"Search term","in":"query","name":"q","schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"description":"next_cursor of the previous result. Opens the next block of 1000 older submissions; page and q work inside the block. A broken cursor fails with 400 invalid_cursor.","in":"query","name":"cursor","schema":{"type":"string"}},{"description":"Comma-separated field names for the columns; default: all fields","in":"query","name":"fields","schema":{"type":"string"}}],"responses":{"200":{"content":{"text/csv":{}},"description":"CSV export"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/submit":{"post":{"description":"Accepts form-encoded or JSON bodies. Special fields: subject, from_name, replyto, redirect, botcheck, consent, ccemail and bccemail (extra recipients separated by ; or ,, only verified addresses of the account), and the captcha token fields altcha, h-captcha-response, cf-turnstile-response, g-recaptcha-response. If the form has a captcha, a missing or wrong token fails with 403 captcha_failed. Files need multipart/form-data: at most 5 files and 10 MB per submission, types csv, docx, gif, heic, jpeg, jpg, odp, ods, odt, pdf, png, pptx, txt, webp, xlsx. They arrive as download links, or as attachments when the form keeps nothing (retention 0). All other fields are delivered by email. A Bearer API key of the form owner skips the origin and captcha checks.","operationId":"submit","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"properties":{"access_key":{"type":"string"}},"required":["access_key"],"type":"object"}},"application/x-www-form-urlencoded":{"schema":{"properties":{"access_key":{"type":"string"}},"type":"object"}},"multipart/form-data":{"schema":{"properties":{"access_key":{"type":"string"}},"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"Delivered (JSON clients); browsers get a 303 redirect instead. body.data holds the submitted fields."},"413":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"file_too_large (more than 10 MB of files per submission) or storage_full (plan storage used up)"},"422":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"file_rejected: more than 5 files, a type outside the allowlist, content that does not match the type, an empty or infected file"},"429":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"rate_limited (5 per minute per form and sender) or limit_reached (monthly quota). Retry-After gives the seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"}}}}},"security":[]}},"/api/submit/{access_key}":{"post":{"description":"Same as POST /api/submit, with the access key in the path instead of the body.","operationId":"submitByPath","parameters":[{"in":"path","name":"access_key","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object"}},"application/x-www-form-urlencoded":{"schema":{"type":"object"}},"multipart/form-data":{"schema":{"type":"object"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"Delivered (JSON clients); browsers get a 303 redirect instead. body.data holds the submitted fields."},"413":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"file_too_large (more than 10 MB of files per submission) or storage_full (plan storage used up)"},"422":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"file_rejected: more than 5 files, a type outside the allowlist, content that does not match the type, an empty or infected file"},"429":{"content":{"application/json":{"schema":{"properties":{"body":{"properties":{"data":{"additionalProperties":{"type":"string"},"type":"object"},"message":{"type":"string"}},"type":"object"},"code":{"description":"Only on errors: api_key_required, captcha_failed, consent_required, delivery_failed, empty_submission, file_rejected, file_too_large, form_disabled, invalid_access_key, invalid_body, limit_reached, origin_not_allowed, rate_limited, scan_failed, storage_full","type":"string"},"message":{"type":"string"},"success":{"type":"boolean"}},"type":"object"}}},"description":"rate_limited (5 per minute per form and sender) or limit_reached (monthly quota). Retry-After gives the seconds to wait.","headers":{"Retry-After":{"schema":{"type":"integer"}}}}},"security":[]}},"/api/websites":{"get":{"operationId":"listWebsites","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Website"},"type":"array"}}},"description":"All websites with their form counts"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"operationId":"createWebsite","requestBody":{"content":{"application/json":{"schema":{"properties":{"domains":{"type":"string"},"name":{"type":"string"}},"required":["name"],"type":"object"}}}},"responses":{"201":{"content":{"application/json":{"schema":{"properties":{"id":{"type":"string"}},"type":"object"}}},"description":"Created"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error","description":"too_many_websites: the plan's limit is reached"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/websites/{id}":{"delete":{"description":"Also deletes the website's forms and submissions.","operationId":"deleteWebsite","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}},"description":"Done"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"operationId":"getWebsite","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Website"}}},"description":"The website"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"patch":{"description":"Changes the defaults for new forms; existing forms keep their settings.","operationId":"updateWebsite","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebsitePatch"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Website"}}},"description":"Updated website"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/websites/{id}/files":{"get":{"description":"Uploaded files of all forms in the website, newest first, up to 500. Links are valid for one hour.","operationId":"listWebsiteFiles","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"properties":{"created_at":{"type":"integer"},"field":{"type":"string"},"form_id":{"type":"string"},"form_name":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"},"size":{"description":"Bytes","type":"integer"},"submission_id":{"type":"string"},"type":{"type":"string"},"url":{"type":"string"}},"type":"object"},"type":"array"}}},"description":"Stored files"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/websites/{id}/integrations":{"get":{"operationId":"listIntegrations","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/Integration"},"type":"array"}}},"description":"Integrations of the website and its forms"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"description":"Every new submission is also sent to Slack, Discord, Telegram or an https URL. Webhooks are signed: Mailiy-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"t.body\"> with the secret. Failed deliveries are retried up to six times within about nine hours. At most 20 per website.","operationId":"createIntegration","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"bot_token":{"description":"Telegram only","type":"string"},"chat_id":{"description":"Telegram only","type":"string"},"form_id":{"description":"Omit for every form of the website","type":"string"},"kind":{"enum":["webhook","slack","discord","telegram"],"type":"string"},"label":{"maxLength":80,"type":"string"},"url":{"description":"webhook, slack and discord: https URL that resolves to a public address","type":"string"}},"required":["kind"],"type":"object"}}}},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Integration"}}},"description":"Created"},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/websites/{id}/submissions":{"get":{"description":"Submissions of all forms in the website, newest first, 25 per page, in blocks of 1000 (see cursor). q searches field names and values.","operationId":"searchWebsiteSubmissions","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"form","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"enum":["delivered","spam"],"type":"string"}},{"description":"Search term","in":"query","name":"q","schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"description":"next_cursor of the previous result. Opens the next block of 1000 older submissions; page and q work inside the block. A broken cursor fails with 400 invalid_cursor.","in":"query","name":"cursor","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"properties":{"keys":{"description":"Field names found in the matches","items":{"type":"string"},"type":"array"},"next_cursor":{"description":"Set when older submissions exist beyond this block","type":["string","null"]},"page":{"type":"integer"},"pages":{"type":"integer"},"rows":{"items":{"allOf":[{"$ref":"#/components/schemas/Submission"},{"properties":{"form_name":{"type":"string"}},"type":"object"}]},"type":"array"},"total":{"type":"integer"}},"type":"object"}}},"description":"One page of matches"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/websites/{id}/submissions.csv":{"get":{"description":"CSV of all matches with the same filters, plus a form column.","operationId":"exportWebsiteSubmissions","parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"string"}},{"in":"query","name":"form","schema":{"type":"string"}},{"in":"query","name":"status","schema":{"enum":["delivered","spam"],"type":"string"}},{"description":"Search term","in":"query","name":"q","schema":{"type":"string"}},{"in":"query","name":"page","schema":{"minimum":1,"type":"integer"}},{"description":"next_cursor of the previous result. Opens the next block of 1000 older submissions; page and q work inside the block. A broken cursor fails with 400 invalid_cursor.","in":"query","name":"cursor","schema":{"type":"string"}},{"description":"Comma-separated field names for the columns; default: all fields","in":"query","name":"fields","schema":{"type":"string"}}],"responses":{"200":{"content":{"text/csv":{}},"description":"CSV export"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"security":[{"bearerAuth":[]}],"servers":[{"url":"https://mailiy.com"}]}