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='3KTM7N9X2QP4R8WVHJ5F6DYC1')

Fetch a batch of entries

ref_nums = ['3KTM7N9X2QP4R8WVHJ5F6DYC1', '7BF2HJ4KN9M3PT6X8DQWYRC5L', '5LR8QV1WN7K4BM2XHTF9GJ3CP']
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='3KTM7N9X2QP4R8WVHJ5F6DYC1', io_stream='/downloads/entry.pdf')

# With timezone localisation:
FormEntry.download_pdf(auth, ref_num='3KTM7N9X2QP4R8WVHJ5F6DYC1', 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.2.tar.gz (25.0 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.2-py3-none-any.whl (21.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: formsmarts-2.1.2.tar.gz
  • Upload date:
  • Size: 25.0 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.2.tar.gz
Algorithm Hash digest
SHA256 30c11b65e00d5881dc87dc13baf5dad015d4767041ecb238f88e7eec6fbcb9db
MD5 305e27c8a9d2cc1524e2d645a97de08a
BLAKE2b-256 56ab22ea9538dc6fe52c7efc5257db26a4e409f59eb4c5d1785ae8cebce23164

See more details on using hashes here.

File details

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

File metadata

  • Download URL: formsmarts-2.1.2-py3-none-any.whl
  • Upload date:
  • Size: 21.6 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4adb80a332fc742f94738cf789def48cd860ac0d7b5ed1701d6e79d116d09e9c
MD5 874d471ee12d08917b51febe14a1868c
BLAKE2b-256 da01e714fa7a924eaaf5736237845b54168a4f3af6c3281d04cb5fdaf92cd3d9

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