Skip to main content

FormSmarts API & Webhook Client

Project description

FormSmarts API & Webhook Client

A Python client for the FormSmarts API and webhook system. Automate form workflows: search and edit submissions, download attachments and PDFs, submit forms programmatically, pre-populate form URLs, and verify incoming webhook callbacks.

Requirements

Installation

pip install formsmarts

Authentication

All API operations require an APIAuthenticator object, initialised with your FormSmarts Account ID and API Key. You can find both in the Security Settings of your account.

from formsmarts import APIAuthenticator

auth = APIAuthenticator('FSA-999999', 'your-api-key')

Security: Never hard-code credentials. Load them from environment variables or a secrets manager.

import os
auth = APIAuthenticator(os.environ['FS_ACCOUNT_ID'], os.environ['FS_API_KEY'])

Searching & Retrieving Entries

Search by date range

Returns a generator of FormEntry objects for all submissions to a form between two dates.

from formsmarts import APIAuthenticator, FormEntry

auth = APIAuthenticator('FSA-999999', 'your-api-key')

for entry in FormEntry.search_by_dates(auth, form_id='lqh', start_date='2025-01-01'):
    print(entry.reference_number, entry.submitted_at)

Search by email, phone, or ID

for entry in FormEntry.search(auth, query='jane@example.com'):
    print(entry.reference_number)

Filter results by tag, and exclude entries with specific tags:

for entry in FormEntry.search(
    auth,
    query='jane@example.com',
    tags='vip',
    exclude_tags=['archived', 'duplicate'],
):
    print(entry.reference_number)

Pass 'latest' as the query to retrieve the most recent submission:

entry = next(FormEntry.search(auth, query='latest', form_id='lqh'))

Fetch a single entry by Reference Number

entry = FormEntry.fetch(auth, ref_num='ABC-123456')

Fetch a batch of entries

ref_nums = ['ABC-123456', 'ABC-123457', 'ABC-123458']
for entry in FormEntry.fetch_batch(auth, form_id='lqh', reference_numbers=ref_nums):
    print(entry.reference_number)

Working with Fields

Iterate over all fields

for field in entry.fields:
    print(field.name, field.type, field.value)

Look up fields by name, type, or ID

# By name (returns a list, as names may not be unique)
email = entry.fields_by_name('Email')[0].value

# By datatype
uploads = entry.fields_by_type('upload')

# By field ID
field = entry.field_by_id(12345)

Field types and their values

Type Field subclass .value returns
text, email, phone, … Field str
boolean (checkbox) Boolean bool
number Field Decimal
posint Field int
date Date datetime.date
option (drop-down, radio) Option str
upload Upload upload ID (str)
signature Signature str
country Country ISO country code (str)
csd (country subdivision) CSD ISO subdivision code (str)

Country and subdivision names

country_field = entry.fields_by_type('country')[0]
print(country_field.country_code)   # 'US'
print(country_field.country_name)   # 'United States'

csd_field = entry.fields_by_type('csd')[0]
print(csd_field.subdivision_code)   # 'NY'
print(csd_field.subdivision_name)   # 'New York'

Edit a field value

Assigning to field.value immediately persists the change via the API.

entry.field_by_id(12345).value = 'Updated text'

This is different from field.set_value(), which only updates the local object (used when building a submission).


Submitting Forms

Simple submission with a field value dict

Keys are field IDs; values are converted to strings automatically.

ref_num = FormEntry.submit(auth, form_id='lqh', field_values={
    122619: 'Jane Doe',
    122620: 'jane@example.com',
    122621: 'Hello, thanks for your help.',
})
print(f'Submitted: {ref_num}')

Submission using Field objects

Use the Form class to look up fields by name, then submit:

from formsmarts import APIAuthenticator, FormEntry, Form

auth = APIAuthenticator('FSA-999999', 'your-api-key')
form = Form(auth, 'lqh')

fields = []
for name, value in {'First Name': 'Jane', 'Last Name': 'Doe'}.items():
    field = form.fields_by_name(name)[0]
    field.set_value(value)
    fields.append(field)

ref_num = FormEntry.submit(auth, 'lqh', fields)

Submission with a file attachment

from formsmarts import Upload

FORM_ID = 'lqh'
UPLOAD_FIELD_ID = 122621

# Step 1: upload the file (accepts a path string or a file-like object)
upload = Upload.upload(
    auth,
    form_id=FORM_ID,
    field_id=UPLOAD_FIELD_ID,
    io_stream='/path/to/report.pdf',
    filename='report.pdf',
)

# Step 2: submit the form, passing the Upload object as the field value
ref_num = FormEntry.submit(auth, FORM_ID, {
    122619: 'Jane Doe',
    122620: 'jane@example.com',
    UPLOAD_FIELD_ID: upload.value,   # the upload ID
})

Attachments & PDFs

Download an attachment

upload_field = entry.fields_by_type('upload')[0]

# To a file path:
upload_field.download(f'/downloads/{upload_field.filename}')

# Or to a file-like object:
upload_field.download(open('copy.pdf', 'wb'))

Replace an attachment

upload_field.replace('/path/to/new-file.pdf', filename='new-file.pdf')

Download an entry as a PDF

FormEntry.download_pdf(auth, ref_num='ABC-123456', io_stream='/downloads/entry.pdf')

# With timezone localisation:
FormEntry.download_pdf(auth, ref_num='ABC-123456', io_stream='/downloads/entry.pdf', timezone='Europe/London')

Tags

# Read tags
print(entry.tags)         # user-defined tags
print(entry.system_tags)  # tags assigned by FormSmarts

# Add and remove tags
entry.add_tag('reviewed')
entry.add_tags(['approved', 'priority'])
entry.remove_tag('reviewed')

Sharing an Entry

Send a copy of a form submission by email:

entry.share('manager@example.com')

# Multiple recipients, with an optional note:
entry.share(
    ['alice@example.com', 'bob@example.com'],
    note='Please review this submission.',
    sign_note=True,
)

Pre-filling Forms

Generate a signed URL that opens the form with fields pre-populated. Optionally lock fields to prevent the user from changing them.

from formsmarts import APIAuthenticator, PreFilledForm
import os

auth = APIAuthenticator(os.environ['FS_ACCOUNT_ID'], os.environ['FS_API_KEY'])
pf = PreFilledForm(auth, form_id='lqh', prefill_key=os.environ['FS_PREFILL_KEY'])

data = {
    'Client ID': {'value': 789034, 'readonly': True},
    'First Name': {'value': 'Jane', 'readonly': True},
    'Last Name':  {'value': 'Doe',  'readonly': True},
    'Email':      {'value': 'jane@example.com'},
}

for name, opts in data.items():
    fields = pf.fields_by_name(name)
    if fields:
        pf.pre_fill(fields[0], opts['value'], readonly=opts.get('readonly', False))

url = pf.get_url()
print(url)

Webhooks

Verifying a webhook callback

Always verify that incoming requests originate from FormSmarts before processing them.

from formsmarts import WebhookAuthenticator, FormEntry, AuthenticationError
import json, os

wh_auth = WebhookAuthenticator(os.environ['FS_WEBHOOK_KEY'])

def handle_webhook(headers, body):
    try:
        wh_auth.verify_request(headers['Authorization'])
    except AuthenticationError:
        return 403

    entry = FormEntry.create(json.loads(body))

    for field in entry.fields:
        print(field.name, field.value)

    return 200

Accessing webhook-only properties

print(entry.submitted_at)       # datetime the form was submitted
print(entry.amount_due)         # for forms with deferred payment
print(entry.is_validation_hook) # True for validation webhooks

Downloading attachments from a webhook

Webhook entries include presigned URLs (valid for 5 minutes) for direct attachment access, in addition to the standard download() method which requires API authentication.

upload_field = entry.fields_by_type('upload')[0]
print(upload_field.presigned_url)  # direct download URL, no auth needed
upload_field.download('/tmp/attachment.pdf')  # uses API auth

Concurrency

The client is synchronous. For workloads that require parallel API calls — such as fetching entries for a dashboard or processing a large batch — use concurrent.futures.ThreadPoolExecutor:

from concurrent.futures import ThreadPoolExecutor, as_completed
from formsmarts import FormEntry

entries = list(FormEntry.search_by_dates(auth, form_id='lqh', start_date='2025-01-01'))

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = {
        executor.submit(
            FormEntry.download_pdf, auth, entry.reference_number,
            f'/downloads/{entry.reference_number}.pdf'
        ): entry
        for entry in entries
    }
    for future in as_completed(futures):
        future.result()  # re-raises any exception from the worker

The client handles HTTP 429 (rate limit) responses automatically with exponential backoff. The default is 3 retries; pass max_retries to the underlying request if you need to adjust this.


Error Handling

from formsmarts import APIRequestError, AuthenticationError

try:
    entry = FormEntry.fetch(auth, ref_num='INVALID')
except AuthenticationError as e:
    print('Auth failed:', e)
except APIRequestError as e:
    print(f'API error {e.status}: {e.text}')
Exception When raised
AuthenticationError Invalid credentials or expired/malformed JWT
APIRequestError Non-200 HTTP response from the API; exposes .status, .text, .json
APIError Base class for all client errors

Links

License

Copyright © Syronex LLC. All rights reserved.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

formsmarts-2.1.0.tar.gz (24.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

formsmarts-2.1.0-py3-none-any.whl (20.8 kB view details)

Uploaded Python 3

File details

Details for the file formsmarts-2.1.0.tar.gz.

File metadata

  • Download URL: formsmarts-2.1.0.tar.gz
  • Upload date:
  • Size: 24.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for formsmarts-2.1.0.tar.gz
Algorithm Hash digest
SHA256 94220296a45e70c455e7f1125fd91de0572aa33c8144048dc08a69188766717d
MD5 02ebe75df09494c934a5c7807b0c6823
BLAKE2b-256 22eefd36b54fd80991e1de996affc625bd63abaf53bb1d09387183d55ba88f66

See more details on using hashes here.

File details

Details for the file formsmarts-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: formsmarts-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for formsmarts-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0e58ac9dbb1fdb29dc5eddd543dad859f49ce2f92ff398b72aac2e4000f69b78
MD5 d9b4302f69693603473782565f1017dd
BLAKE2b-256 e8ca093f48e00b3e17cd572a12bf0b15133f6133a1ff8c5255432bd7955bfb4d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page