{"openapi":"3.1.0","info":{"title":"Formshive API","description":"Form processing service with email notifications, file uploads, and integration capabilities","contact":{"name":"Formshive Support","email":"support@formshive.com"},"license":{"name":"MIT"},"version":"1.0.0"},"servers":[{"url":"http://localhost:8700","description":"Local development server"},{"url":"https://api.formshive.com","description":"Production server"}],"paths":{"/v1/a/analytics/aggregate":{"get":{"tags":["analytics"],"summary":"Get aggregated analytics data for forms","description":"Returns top countries, cities, browsers, and average time between view and submission\nfor the authenticated user's forms. Supports optional filtering by form IDs and spam status.\n\nQuery Parameters:\n- `form_ids`: Optional comma-separated list of form UUIDs to filter by\n- `is_spam`: Optional boolean to filter by spam status (true=spam only, false=non-spam only, omit=all)\n\nExample: `/v1/a/analytics/aggregate?form_ids=uuid1,uuid2&is_spam=false`","operationId":"get_analytics_aggregate","parameters":[{"name":"form_ids","in":"query","required":false,"schema":{"type":["array","null"],"items":{"type":"string"}}},{"name":"is_spam","in":"query","required":false,"schema":{"type":["boolean","null"]}}],"responses":{"200":{"description":"Analytics data retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormAnalyticsAggregateResponse"}}}},"401":{"description":"Unauthorized"},"500":{"description":"Internal server error"}},"security":[{"bearerAuth":[]}]}},"/v1/a/balance":{"get":{"tags":["billing"],"operationId":"account_balance","responses":{"200":{"description":"Account balance information"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/deposits":{"get":{"tags":["billing"],"operationId":"get_deposits","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}}],"responses":{"200":{"description":"List of deposits"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["billing"],"operationId":"new_deposit","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewDepositHttp"}}},"required":true},"responses":{"201":{"description":"Deposit created successfully"},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/deposits/{deposit_id}":{"get":{"tags":["billing"],"operationId":"get_deposit","parameters":[{"name":"deposit_id","in":"path","description":"Deposit ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deposit details"},"401":{"description":"Unauthorized - not deposit owner"},"404":{"description":"Deposit not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/files":{"get":{"tags":["files"],"operationId":"get_files_endpoint","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"form_id","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"message_id","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"200":{"description":"List of uploaded files"},"401":{"description":"Unauthorized - invalid or missing auth token"},"404":{"description":"Form not found or not owned by user"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/files/download/{file_id}":{"get":{"tags":["files"],"operationId":"download_file","parameters":[{"name":"file_id","in":"path","description":"File ID","required":true,"schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to signed download URL"},"401":{"description":"Unauthorized - not file owner"},"404":{"description":"File not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/files/{file_id}":{"get":{"tags":["files"],"operationId":"get_file_endpoint","parameters":[{"name":"file_id","in":"path","description":"File ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"File details"},"401":{"description":"Unauthorized - not file owner"},"404":{"description":"File not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["files"],"operationId":"delete_file_endpoint","parameters":[{"name":"file_id","in":"path","description":"File ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"File deleted successfully"},"401":{"description":"Unauthorized - not file owner"},"404":{"description":"File not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms":{"get":{"tags":["forms"],"operationId":"get_forms","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}}],"responses":{"200":{"description":"List of forms","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Form"}}}}},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"post":{"tags":["forms"],"operationId":"create_form","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HttpNewForm"}}},"required":true},"responses":{"201":{"description":"Form created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"400":{"description":"Bad request - validation error"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms/recipients":{"post":{"tags":["recipients"],"operationId":"new_form_recipient","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewFormsRecipient"}}},"required":true},"responses":{"201":{"description":"Form recipient added successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormsRecipient"}}}},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized - invalid auth token or not email owner"},"404":{"description":"Form or email not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms/{form_id}":{"get":{"tags":["forms"],"operationId":"get_form","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Form details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Form"}}}},"401":{"description":"Unauthorized - not form owner"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["forms"],"operationId":"delete_form","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Form deleted successfully"},"401":{"description":"Unauthorized - not form owner"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["forms"],"operationId":"update_form","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateForm"}}},"required":true},"responses":{"200":{"description":"Form updated successfully"},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized - not form owner"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms/{form_id}/recipients":{"get":{"tags":["recipients"],"operationId":"get_forms_recipients","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}}],"responses":{"200":{"description":"List of form recipients"},"401":{"description":"Unauthorized - invalid auth token or not form owner"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms/{form_id}/recipients/{recipient_id}":{"delete":{"tags":["recipients"],"operationId":"delete_form_recipient","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}},{"name":"recipient_id","in":"path","description":"Recipient email ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Form recipient deleted successfully"},"401":{"description":"Unauthorized - invalid auth token or not form owner"},"404":{"description":"Form or recipient not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/forms/{form_id}/specs":{"patch":{"tags":["forms"],"operationId":"update_form_specs","parameters":[{"name":"form_id","in":"path","description":"Form ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFormSpecs"}}},"required":true},"responses":{"200":{"description":"Form specs updated successfully"},"400":{"description":"Bad request - validation error"},"401":{"description":"Unauthorized - not form owner"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/logout":{"post":{"tags":["auth"],"operationId":"logout","responses":{"200":{"description":"Logout successful"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/messages":{"get":{"tags":["messages"],"operationId":"get_messages","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"form_id","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"is_spam","in":"query","required":false,"schema":{"type":["boolean","null"]}}],"responses":{"200":{"description":"List of form submission messages","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessagesApiResponse"}}}},"401":{"description":"Unauthorized - invalid or missing auth token"},"404":{"description":"Form not found or not owned by user"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/messages/bulk":{"delete":{"tags":["messages"],"operationId":"bulk_delete_messages","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HttpBulkDeleteMessages"}}},"required":true},"responses":{"200":{"description":"Messages deleted, with per-id outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMessagesResponse"}}}},"400":{"description":"Empty or oversized batch"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["messages"],"operationId":"bulk_update_messages","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HttpBulkUpdateMessages"}}},"required":true},"responses":{"200":{"description":"Messages marked, with per-id outcome","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkMessagesResponse"}}}},"400":{"description":"Empty or oversized batch"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/messages/count-by-day":{"get":{"tags":["messages"],"operationId":"get_messages_count_by_day","parameters":[{"name":"form_ids","in":"query","required":false,"schema":{"type":["array","null"],"items":{"type":"string"}}},{"name":"is_spam","in":"query","required":false,"schema":{"type":["boolean","null"]}},{"name":"data_type","in":"query","required":false,"schema":{"$ref":"#/components/schemas/DataType"}}],"responses":{"200":{"description":"Form analytics statistics by day - messages, views, or combined data with conversion rates"},"400":{"description":"Bad request - invalid form ID, data_type, or parameters"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/messages/{message_id}":{"get":{"tags":["messages"],"operationId":"get_message","parameters":[{"name":"message_id","in":"path","description":"Message ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Single message with form and files","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SingleMessageResponse"}}}},"401":{"description":"Unauthorized - invalid or missing auth token"},"404":{"description":"Message not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"delete":{"tags":["messages"],"operationId":"delete_message","parameters":[{"name":"message_id","in":"path","description":"Message ID","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Message deleted successfully"},"401":{"description":"Unauthorized - not message owner"},"404":{"description":"Message not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]},"patch":{"tags":["messages"],"summary":"\n * Using this endpoint, a user can mark a message as spam, or ham.\n * - The first time the user changes `is_spam`, we set `user_marked_spam` to true.\n * - Subsequent changes to `is_spam` will not change `user_marked_spam`.","operationId":"update_message","parameters":[{"name":"message_id","in":"path","description":"Message ID","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HttpUpdateMessage"}}},"required":true},"responses":{"200":{"description":"Message updated successfully, may include notification count"},"401":{"description":"Unauthorized - not message owner"},"404":{"description":"Message not found"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/movements":{"get":{"tags":["billing"],"operationId":"get_account_movements","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}},{"name":"offset","in":"query","required":false,"schema":{"type":["integer","null"],"format":"int64"}}],"responses":{"200":{"description":"Account movement history"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"}},"security":[{"bearer_auth":[]}]}},"/v1/a/whoami":{"get":{"tags":["auth"],"summary":"Fresh identity snapshot for the authenticated user. OIDC sessions decrypt\nthe cached `id_token` (no IdP round-trip); local sessions read users.","operationId":"whoami","responses":{"200":{"description":"Fresh identity claims for the authenticated user"},"401":{"description":"Unauthorized - invalid or missing auth token"},"500":{"description":"Internal server error"},"502":{"description":"Identity provider unreachable"}},"security":[{"bearer_auth":[]}]}},"/v1/digest/{form_id}":{"post":{"tags":["processing"],"operationId":"new_message","parameters":[{"name":"form_id","in":"path","description":"The ID of the form to submit to","required":true,"schema":{"type":"string"}},{"name":"css_framework","in":"query","description":"CSS framework to use for styling the form response. Options: 'bulma', 'bootstrap', or 'formshive'.\nWhen specified, applies framework-specific CSS classes to form elements and error messages.","required":false,"schema":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/CssFramework"}]}},{"name":"css_embed","in":"query","description":"Whether to embed CSS styles directly in the HTML response instead of using external links.\nUseful for self-contained HTML responses or when external CSS may not be accessible.","required":false,"schema":{"type":["boolean","null"]}},{"name":"iframe","in":"query","description":"Indicates if the form is being displayed within an iframe.\nMay affect how responses are formatted or styled for iframe compatibility.","required":false,"schema":{"type":["boolean","null"]}},{"name":"track","in":"query","description":"Enable analytics tracking for this form submission.\nWhen true, captures additional metrics like submission timing and user behavior data.","required":false,"schema":{"type":["boolean","null"]}},{"name":"redirect","in":"query","description":"Override the form's default redirect behavior after successful submission.\nOptions: 'html' - Show success HTML page, 'none' - Return JSON response without redirect.","required":false,"schema":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FormRedirectOverride"}]}}],"requestBody":{"content":{"application/json":{"schema":{}}},"required":true},"responses":{"201":{"description":"Message created successfully"},"302":{"description":"Redirect to form's redirect URL (non-JSON requests)"},"400":{"description":"Bad request - validation error or invalid challenge"},"402":{"description":"Payment required - insufficient balance"},"404":{"description":"Form not found"},"500":{"description":"Internal server error"}}}},"/v1/login":{"post":{"tags":["auth"],"operationId":"login","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Login challenge created successfully"},"400":{"description":"Bad request - invalid login data"},"500":{"description":"Internal server error"}}}},"/v1/login/challenge":{"post":{"tags":["auth"],"operationId":"login_challenge","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginChallengeUserResponse"}}},"required":true},"responses":{"200":{"description":"Authentication successful, returns access and refresh tokens"},"400":{"description":"Bad request - invalid challenge response"},"401":{"description":"Unauthorized - challenge failed"},"500":{"description":"Internal server error"}}}},"/v1/login/challenge/github":{"get":{"tags":["auth"],"operationId":"login_challenge_github","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}},{"name":"state","in":"query","required":true,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"302":{"description":"Redirect to frontend with authentication tokens"},"400":{"description":"Bad request - invalid GitHub response"},"500":{"description":"Internal server error"}}}},"/v1/login/challenge/google":{"get":{"tags":["auth"],"operationId":"login_challenge_google","parameters":[{"name":"code","in":"query","required":true,"schema":{"type":"string"}},{"name":"state","in":"query","required":true,"schema":{"type":"string"}},{"name":"scope","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"authuser","in":"query","required":false,"schema":{"type":["string","null"]}},{"name":"prompt","in":"query","required":false,"schema":{"type":["string","null"]}}],"responses":{"302":{"description":"Redirect to frontend with authentication tokens"},"400":{"description":"Bad request - invalid Google response"},"500":{"description":"Internal server error"}}}},"/v1/login/refresh":{"post":{"tags":["auth"],"operationId":"login_refresh","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshSessionRequest"}}},"required":true},"responses":{"200":{"description":"Tokens refreshed successfully"},"400":{"description":"Bad request - invalid refresh token"},"401":{"description":"Unauthorized - refresh token expired or invalid"},"500":{"description":"Internal server error"}}}}},"components":{"schemas":{"AnalyticsAggregateQueryParams":{"type":"object","properties":{"form_ids":{"type":["array","null"],"items":{"type":"string"}},"is_spam":{"type":["boolean","null"]}}},"AverageTimeData":{"type":"object","required":["average_time_seconds"],"properties":{"average_time_seconds":{"type":"number","format":"double"}}},"BrowserData":{"type":"object","required":["browser","count"],"properties":{"browser":{"type":"string"},"count":{"type":"integer","format":"int64"}}},"BulkMessagesResponse":{"type":"object","required":["succeeded","failed","notified_recipients"],"properties":{"failed":{"type":"array","items":{"type":"string"},"description":"Ids left untouched: missing, owned by someone else, or the operation\nfailed on them. One bad id does not fail the batch."},"notified_recipients":{"type":"integer","format":"int64"},"succeeded":{"type":"integer","format":"int64"}}},"CityData":{"type":"object","required":["city","count"],"properties":{"city":{"type":"string"},"count":{"type":"integer","format":"int64"}}},"CommonQueryParams":{"type":"object","properties":{"limit":{"type":["integer","null"],"format":"int64"},"offset":{"type":["integer","null"],"format":"int64"}}},"CountryData":{"type":"object","required":["country","count"],"properties":{"count":{"type":"integer","format":"int64"},"country":{"type":"string"}}},"CssFramework":{"type":"string","description":"CSS frameworks supported for form styling and response formatting","enum":["bulma","bootstrap","formshive"]},"Currency":{"type":"string","enum":["btc","eur","usdt","dai","usd","aed","ars","aud","bdt","bgn","bob","brl","cad","chf","clp","cny","cop","crc","czk","dkk","dop","egp","fjd","gbp","hkd","hrk","huf","idr","ils","inr","jpy","kes","krw","lkr","mad","mxn","myr","ngn","nok","nzd","pen","php","pkr","pln","qar","ron","rub","sar","sek","sgd","thb","try","twd","uah","vnd","zar"]},"DepositProvider":{"type":"string","enum":["STRIPE","COINBASE"]},"DepositStatus":{"type":"string","enum":["PENDING","DONE","ERROR"]},"File":{"type":"object","required":["id","form_id","message_id","filename","bucket_name","path","size","created_at"],"properties":{"bucket_name":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"filename":{"type":"string"},"form_id":{"type":"string"},"id":{"type":"string"},"message_id":{"type":"string"},"path":{"type":"string"},"size":{"type":"integer","format":"int32"}}},"FileQueryParams":{"type":"object","properties":{"form_id":{"type":["string","null"]},"limit":{"type":["integer","null"],"format":"int64"},"message_id":{"type":["string","null"]},"offset":{"type":["integer","null"],"format":"int64"}}},"Form":{"type":"object","required":["id","title","user_id","filter_spam","created_at","updated_at","check_specs","check_challenge","auto_response_enabled","encryption_mode","email_notification_type","email_notification_format","auto_response_format","allowed_origins"],"properties":{"allowed_origins":{"type":"array","items":{"type":"string"},"description":"Empty accepts submissions from anywhere, so existing forms are unaffected."},"auto_response_enabled":{"type":"boolean"},"auto_response_format":{"type":"string","description":"'html' | 'text'"},"auto_response_subject":{"type":["string","null"]},"auto_response_text":{"type":["string","null"]},"check_challenge":{"type":"boolean"},"check_specs":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"email_notification_format":{"type":"string","description":"'html' | 'text'"},"email_notification_type":{"type":"string","description":"'full' (includes content) | 'notification' (link only). Forced to\n'notification' in nostr mode."},"encryption_mode":{"type":"string","description":"'none' | 'server' | 'nostr' — immutable after creation."},"filter_spam":{"type":"boolean"},"id":{"type":"string"},"redirect_url":{"type":["string","null"]},"retention_days":{"type":["integer","null"],"format":"int32","description":"None means no auto-cleanup, and serializes as `null` rather than being\nomitted, so the emitted TS has to admit it."},"specs":{"type":["string","null"]},"title":{"type":"string"},"updated_at":{"type":"string","format":"date-time"},"user_id":{"type":"string"}}},"FormAnalyticsAggregateResponse":{"type":"object","required":["top_countries","top_cities","top_browsers","average_time_to_submit_message"],"properties":{"average_time_to_submit_message":{"$ref":"#/components/schemas/AverageTimeData"},"top_browsers":{"type":"array","items":{"$ref":"#/components/schemas/BrowserData"}},"top_cities":{"type":"array","items":{"$ref":"#/components/schemas/CityData"}},"top_countries":{"type":"array","items":{"$ref":"#/components/schemas/CountryData"}}}},"FormHtmlOptions":{"type":"object","description":"Query parameters for customizing form submission behavior and HTML rendering","properties":{"css_embed":{"type":["boolean","null"],"description":"Whether to embed CSS styles directly in the HTML response instead of using external links.\nUseful for self-contained HTML responses or when external CSS may not be accessible."},"css_framework":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/CssFramework","description":"CSS framework to use for styling the form response. Options: 'bulma', 'bootstrap', or 'formshive'.\nWhen specified, applies framework-specific CSS classes to form elements and error messages."}]},"iframe":{"type":["boolean","null"],"description":"Indicates if the form is being displayed within an iframe.\nMay affect how responses are formatted or styled for iframe compatibility."},"redirect":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FormRedirectOverride","description":"Override the form's default redirect behavior after successful submission.\nOptions: 'html' - Show success HTML page, 'none' - Return JSON response without redirect."}]},"track":{"type":["boolean","null"],"description":"Enable analytics tracking for this form submission.\nWhen true, captures additional metrics like submission timing and user behavior data."}}},"FormRecipientsQueryParams":{"type":"object","properties":{"limit":{"type":["integer","null"],"format":"int64"},"offset":{"type":["integer","null"],"format":"int64"}}},"FormRedirectOverride":{"type":"string","description":"Override options for form submission redirect behavior.\nBy default, if a redirect URL is set on the form, submissions will redirect to that URL.\nThese options allow overriding that default behavior on a per-request basis.","enum":["html","none"]},"FormView":{"type":"object","required":["id","form_id","ip_hash","view_type","created_at"],"properties":{"analytics_data":{},"browser_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"device_type":{"type":["string","null"]},"form_id":{"type":"string"},"id":{"type":"string"},"ip_hash":{"type":"string"},"location":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/LocationInfo"}]},"referrer":{"type":["string","null"]},"traffic_source":{"type":["string","null"]},"user_agent":{"type":["string","null"]},"view_type":{"type":"string"}}},"FormsQueryParams":{"type":"object","properties":{"limit":{"type":["integer","null"],"format":"int64"},"offset":{"type":["integer","null"],"format":"int64"}}},"FormsRecipient":{"type":"object","required":["form_id","verified_email_id","created_at"],"properties":{"created_at":{"type":"string","format":"date-time"},"form_id":{"type":"string"},"verified_email_id":{"type":"string"}}},"GitHubLoginChallengeResponse":{"type":"object","required":["code","state"],"properties":{"code":{"type":"string"},"scope":{"type":["string","null"]},"state":{"type":"string"}}},"GitHubLoginRequest":{"type":"object","properties":{"referral_code":{"type":["string","null"]}}},"GoogleLoginChallengeResponse":{"type":"object","required":["code","state"],"properties":{"authuser":{"type":["string","null"]},"code":{"type":"string"},"prompt":{"type":["string","null"]},"scope":{"type":["string","null"]},"state":{"type":"string"}}},"GoogleLoginRequest":{"type":"object","properties":{"referral_code":{"type":["string","null"]}}},"HttpBulkDeleteMessages":{"type":"object","required":["message_ids"],"properties":{"message_ids":{"type":"array","items":{"type":"string"}}}},"HttpBulkUpdateMessages":{"type":"object","required":["message_ids","is_spam"],"properties":{"is_spam":{"type":"boolean"},"message_ids":{"type":"array","items":{"type":"string"}}}},"HttpNewForm":{"type":"object","required":["title"],"properties":{"allowed_origins":{"type":["array","null"],"items":{"type":"string"}},"auto_response_enabled":{"type":["boolean","null"]},"auto_response_format":{"type":["string","null"]},"auto_response_subject":{"type":["string","null"]},"auto_response_text":{"type":["string","null"]},"check_challenge":{"type":["boolean","null"]},"email_notification_format":{"type":["string","null"]},"email_notification_type":{"type":["string","null"],"description":"Forced to 'notification' in nostr mode."},"encryption_mode":{"type":["string","null"],"description":"'nostr' requires the user to have a Nostr public_key."},"filter_spam":{"type":["boolean","null"]},"redirect_url":{"type":["string","null"]},"retention_days":{"type":["integer","null"],"format":"int32","description":"None = use plan default."},"title":{"type":"string"}}},"HttpUpdateMessage":{"type":"object","required":["is_spam"],"properties":{"is_spam":{"type":"boolean"}}},"LocationInfo":{"type":"object","properties":{"city":{"type":["string","null"]},"continent_code":{"type":["string","null"]},"continent_name":{"type":["string","null"]},"country_code":{"type":["string","null"]},"country_name":{"type":["string","null"]},"latitude":{"type":["number","null"],"format":"double"},"longitude":{"type":["number","null"],"format":"double"},"region_code":{"type":["string","null"]},"region_name":{"type":["string","null"]},"timezone":{"type":["string","null"]}}},"LoginChallengeUserResponse":{"oneOf":[{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/NostrLoginChallengeResponse"},"type":{"type":"string","enum":["NOSTR"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/MagicLinkLoginChallengeResponse"},"type":{"type":"string","enum":["EMAIL_MAGIC_LINK"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/GoogleLoginChallengeResponse"},"type":{"type":"string","enum":["GOOGLE"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/GitHubLoginChallengeResponse"},"type":{"type":"string","enum":["GITHUB"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/MicrosoftLoginChallengeResponse"},"type":{"type":"string","enum":["MICROSOFT"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/OidcLoginChallengeResponse"},"type":{"type":"string","enum":["OIDC"]}}}]},"LoginRequest":{"oneOf":[{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/NostrLoginRequest"},"type":{"type":"string","enum":["NOSTR"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/MagicLinkLoginReqest"},"type":{"type":"string","enum":["EMAIL_MAGIC_LINK"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/GoogleLoginRequest"},"type":{"type":"string","enum":["GOOGLE"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/GitHubLoginRequest"},"type":{"type":"string","enum":["GITHUB"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/MicrosoftLoginRequest"},"type":{"type":"string","enum":["MICROSOFT"]}}},{"type":"object","required":["content","type"],"properties":{"content":{"$ref":"#/components/schemas/OidcLoginRequest"},"type":{"type":"string","enum":["OIDC"]}}}]},"MagicLinkLoginChallengeResponse":{"type":"object","required":["id","challenge"],"properties":{"challenge":{"type":"string"},"id":{"type":"string"}}},"MagicLinkLoginReqest":{"type":"object","required":["email"],"properties":{"email":{"type":"string"},"referral_code":{"type":["string","null"]}}},"Message":{"type":"object","required":["id","form_id","data","is_spam","spam_score","user_marked_spam","created_at","ip_hash","encryption_mode"],"properties":{"browser_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"string","description":"Plaintext JSON in 'none' mode; ciphertext in 'server'/'nostr' modes."},"device_type":{"type":["string","null"]},"encryption_mode":{"type":"string"},"encryption_pubkey":{"type":["string","null"],"description":"Nostr mode: ephemeral server pubkey the user needs to decrypt."},"form_id":{"type":"string"},"form_view_id":{"type":["string","null"]},"id":{"type":"string"},"ip_hash":{"type":"string"},"is_spam":{"type":"boolean"},"location":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/LocationInfo"}]},"signal_hits":{"description":"Which signals fired, so an owner can be told why a message was marked."},"spam_score":{"type":"number","format":"float"},"user_agent":{"type":["string","null"]},"user_marked_spam":{"type":"boolean"}}},"MessageQueryParams":{"type":"object","properties":{"form_id":{"type":["string","null"]},"is_spam":{"type":["boolean","null"]},"limit":{"type":["integer","null"],"format":"int64"},"offset":{"type":["integer","null"],"format":"int64"}}},"MessageResponse":{"type":"object","description":"Response type for messages that handles encryption modes appropriately.\n- For 'none' and 'server' modes: data contains decrypted JSON\n- For 'nostr' mode: data_encrypted contains the ciphertext, data is null","required":["id","form_id","is_spam","spam_score","user_marked_spam","created_at","ip_hash","encryption_mode"],"properties":{"browser_name":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":["string","null"],"description":"Decrypted message data (JSON string). Present for 'none' and 'server' modes, null for 'nostr' mode."},"data_encrypted":{"type":["string","null"],"description":"Encrypted message data. Only present for 'nostr' mode."},"device_type":{"type":["string","null"]},"encryption_mode":{"type":"string","description":"Encryption mode used for this message"},"encryption_pubkey":{"type":["string","null"],"description":"For Nostr mode: the ephemeral server pubkey for decryption"},"form_id":{"type":"string"},"form_view_id":{"type":["string","null"]},"id":{"type":"string"},"ip_hash":{"type":"string"},"is_spam":{"type":"boolean"},"location":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/LocationInfo"}]},"spam_score":{"type":"number","format":"float"},"user_agent":{"type":["string","null"]},"user_marked_spam":{"type":"boolean"}}},"MessagesApiResponse":{"type":"object","description":"Response type for paginated messages","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/MessageResponse"}},"files":{"type":["array","null"],"items":{"$ref":"#/components/schemas/File"}},"total":{"type":"integer","format":"int64"}}},"MicrosoftLoginChallengeResponse":{"type":"object","required":["code","state"],"properties":{"code":{"type":"string"},"scope":{"type":["string","null"]},"session_state":{"type":["string","null"]},"state":{"type":"string"}}},"MicrosoftLoginRequest":{"type":"object","properties":{"referral_code":{"type":["string","null"]}}},"NewDepositHttp":{"type":"object","required":["amount","currency","provider"],"properties":{"amount":{"type":"integer","format":"int64","description":"Amount in cents (e.g., 1500 for 15.00 EUR, 1 for 0.01 EUR)"},"currency":{"$ref":"#/components/schemas/Currency"},"provider":{"$ref":"#/components/schemas/DepositProvider"}}},"NewForm":{"type":"object","required":["title","user_id"],"properties":{"allowed_origins":{"type":["array","null"],"items":{"type":"string"}},"auto_response_enabled":{"type":["boolean","null"]},"auto_response_format":{"type":["string","null"]},"auto_response_subject":{"type":["string","null"]},"auto_response_text":{"type":["string","null"]},"check_challenge":{"type":["boolean","null"]},"email_notification_format":{"type":["string","null"]},"email_notification_type":{"type":["string","null"]},"encryption_mode":{"type":["string","null"]},"filter_spam":{"type":["boolean","null"]},"redirect_url":{"type":["string","null"]},"retention_days":{"type":["integer","null"],"format":"int32","description":"None = use plan default."},"title":{"type":"string"},"user_id":{"type":"string"}}},"NewFormsRecipient":{"type":"object","required":["form_id","verified_email_id"],"properties":{"form_id":{"type":"string"},"verified_email_id":{"type":"string"}}},"NostrLoginChallengeResponse":{"type":"object","required":["id","response"],"properties":{"id":{"type":"string"},"response":{}}},"NostrLoginRequest":{"type":"object","required":["public_key"],"properties":{"public_key":{"type":"string"},"referral_code":{"type":["string","null"]}}},"OidcLoginChallengeResponse":{"type":"object","required":["state"],"properties":{"code":{"type":["string","null"],"description":"Auth code returned by the IdP on a successful authorize. Absent when\nthe IdP redirects back with an error (`?error=...&state=...`)."},"error":{"type":["string","null"],"description":"Error code per RFC 6749 §4.1.2.1 (e.g. `access_denied`,\n`login_required`) when the IdP could not return a code."},"error_description":{"type":["string","null"],"description":"Human-readable error description. Attacker-controlled — log\nserver-side, never echo into redirect URLs."},"iss":{"type":["string","null"],"description":"RFC 9207 authorization-response `iss`. When present we check it against\nthe configured issuer in the link/step-up callbacks (defense-in-depth\non the public callback)."},"scope":{"type":["string","null"]},"state":{"type":"string","description":"State (CSRF token) for verification. Always present so we can identify\nand clean up the cached challenge."}}},"OidcLoginRequest":{"type":"object","properties":{"referral_code":{"type":["string","null"]},"return_to":{"type":["string","null"],"description":"Optional absolute URL the OIDC callback should redirect to after the\nflow completes, instead of the default `/#/login/callback?...`. Used by\nthe refresh-on-return flow to land the user back on the same account\npage they came from. The frontend MUST pass a URL whose origin matches\nthe configured frontend; the backend validates before honouring."}}},"RefreshSessionRequest":{"type":"object","description":"Body of `POST /v1/login/refresh`. Browsers send no body — the session\ncookie is read directly from the request. Native SDK / CLI clients\nPOST `{ \"session_token\": \"gf_...\" }`; the field is optional so the\nendpoint handler can still try the cookie + Authorization header before\nfalling back to the body.","properties":{"session_token":{"type":["string","null"]}}},"SingleMessageResponse":{"type":"object","description":"Single message response with form and files","required":["message","files"],"properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/File"}},"form":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Form"}]},"message":{"$ref":"#/components/schemas/MessageResponse"},"view":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/FormView"}]}}},"UpdateForm":{"type":"object","required":["title","filter_spam","check_challenge","check_specs","auto_response_enabled"],"properties":{"allowed_origins":{"type":["array","null"],"items":{"type":"string"}},"auto_response_enabled":{"type":"boolean"},"auto_response_format":{"type":["string","null"]},"auto_response_subject":{"type":["string","null"]},"auto_response_text":{"type":["string","null"]},"check_challenge":{"type":"boolean"},"check_specs":{"type":"boolean"},"email_notification_format":{"type":["string","null"]},"filter_spam":{"type":"boolean"},"redirect_url":{"type":["string","null"]},"retention_days":{"type":["integer","null"],"format":"int32","description":"None = use plan default."},"specs":{"type":["string","null"]},"title":{"type":"string"}}},"UpdateFormSpecs":{"type":"object","properties":{"specs":{"type":["string","null"]}}},"WhoamiResponse":{"type":"object","description":"Fresh identity claims for the authenticated user. Returned by\n`GET /v1/a/whoami`. For OIDC users, populated by decrypting + parsing the\n`id_token` stored on the session row (no IdP round-trip). For local users,\nderived from the `users` table profile.","required":["user_id","login_method"],"properties":{"display_name":{"type":["string","null"]},"email":{"type":["string","null"]},"login_method":{"type":"string","description":"`\"oidc\"` for OIDC-backed sessions, `\"local\"` for everyone else\n(Nostr, magic link, Google/GitHub/Microsoft OAuth — all carry a\nlocally-issued JWT and store identity locally)."},"picture":{"type":["string","null"]},"user_id":{"type":"string"}}}},"securitySchemes":{"bearer_auth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"tags":[{"name":"forms","description":"Form management endpoints"},{"name":"messages","description":"Form submission messages"},{"name":"recipients","description":"Form recipient management"},{"name":"integrations","description":"Integration management"},{"name":"files","description":"File upload and management"},{"name":"processing","description":"Form processing endpoints"},{"name":"auth","description":"Authentication endpoints"},{"name":"billing","description":"Billing and payment endpoints"},{"name":"analytics","description":"Analytics and reporting endpoints"}]}