Model Routing

UiForm provides intelligent model routing through two special model identifiers: auto-large and auto-small. These models automatically route your requests to the current best-performing model based on availability, performance, and speed metrics. This means you don’t need to manually update your model selection when new, better-performing models become available - UiForm handles the routing for you, ensuring your applications always use the optimal model for your use case.

Sync & Async Client

UiForm offers both synchronous and asynchronous client interfaces, making it versatile for different application needs. The asynchronous client (AsyncUiForm) is ideal for high-performance, non-blocking applications where multiple tasks run concurrently. For simpler or blocking operations, the synchronous client (UiForm) provides a straightforward approach.

Here’s how you can use both:

# Async client
from uiform import AsyncUiForm

async def fetch_models():
    uiclient = AsyncUiForm()
    models = await uiclient.models.list()
    print(models)

# Sync client
from uiform import UiForm

client = UiForm()
models = client.models.list()
print(models)

Both clients provide the same core functionality, enabling you to list models, create messages, extract data from documents, and more, with the flexibility to match your application’s concurrency model.

Pagination

Many top-level resources have support for bulk fetches via list API methods. For instance, you can list extraction links, list email addresses, and list logs. These list API methods share a common structure, taking at least these four parameters: limit, order, after, and before.

UiForm utilizes pagination via the after and before parameters. Both parameters take an existing object ID value and return objects in either descending or ascending order by creation time.

Idempotency

The UiForm API supports idempotency which guarantees that performing the same operation multiple times will have the same result as if the operation were performed only once. This is handy in situations where you may need to retry a request due to a failure or prevent accidental duplicate requests from creating more than one resource.

To achieve idempotency, you can add Idempotency-Key request header to any UiForm API request with a unique string as the value. Each subsequent request matching this unique string will return the same response. We suggest using v4 UUIDs for idempotency keys to avoid collisions.

Idempotency key example
curl --request POST \
  --url https://api.uiform.com/v1/emails/tests/webhook \
  -H "Authorization: Bearer sk_test_a2V5XzAxSkgwVjhSN1ZaRTlYUzJYQzhOOTVRVDMzLEJSa3BzTEFuUTRVUWF5dEV5ZHpnRVZpVkI" \
  -H "Idempotency-Key: cd320c5c-e928-4212-a5bd-986c29362867" \

Idempotency keys expire after 24 hours. The UiForm API will generate a new response if you submit a request with an expired key.

Rate Limits

UiForm implements rate limiting to ensure stable service for all users. The API uses a rolling window rate limit with the following configuration:

  • 300 requests per 60-second window
  • Applies across the following API endpoints:
    • POST /v1/documents/extractions
    • POST /v1/documents/create_messages

When you exceed the rate limit, the API will return a 429 Too Many Requests response. The response headers will include:

Status 429 - {'detail': 'Rate limit exceeded. Please try again later.'}

For high-volume applications, we can provide a dedicated plan. Contact us for more information.

Modality

LLM works with text and image data. UiForm converts documents into different modalities, based on the document type.

Native modalities

Here are the list of native modalities supported by UiForm:

TEXT_TYPES = Literal[".txt", ".csv", ".tsv", ".md", ".log", ".html", ".htm", ".xml", ".json", ".yaml", ".yml", ".rtf", ".ini", ".conf", ".cfg", ".nfo", ".srt", ".sql", ".sh", ".bat", ".ps1", ".js", ".jsx", ".ts", ".tsx", ".py", ".java", ".c", ".cpp", ".cs", ".rb", ".php", ".swift", ".kt", ".go", ".rs", ".pl", ".r", ".m", ".scala"]

You can also use the modality parameter to specify the modality of the document and override the default modality.

import json
from uiform.client import UiForm

with open("booking_confirmation_json_schema.json", "r") as f:
    json_schema = json.load(f)

uiclient = UiForm()

response = uiclient.documents.extractions.parse(
    json_schema = json_schema,
    document="booking_confirmation.jpg",
    model="gpt-4.1-nano",
    temperature=0,
    modality='text' # The image will be converted to text (with an OCR model) before being sent to the LLM
)

Image Settings

When processing images, several factors can affect the LLM’s ability to accurately interpret and extract information. The image_resolution_dpi and browser_canvas parameters allow you to tune images settings to improve extraction quality.

API Reference

image_resolution_dpi
integer

The DPI of the image. Defaults to 96.

browser_canvas
string

The canvas size of the browser. Must be one of:

  • “A3” (11.7in x 16.54in)
  • “A4” (8.27in x 11.7in)
  • “A5” (5.83in x 8.27in) Defaults to “A4”.

Consensus

You can leverage the consensus feature to improve the accuracy of the extraction. The consensus feature is a way to aggregate the results of multiple LLMs to improve the accuracy of the extraction.

The consensus principle is simple: Multiple runs should give the same result, if the result is not the same, the LLM is not confident about the result so neither should you. We compute a consensus score for each field.

Some additional _consensus_score fields are added to the likelihoods object, they are computed as the average of the consensus scores within some context.

import json
from uiform.client import UiForm

with open("booking_confirmation_json_schema.json", "r") as f:
    json_schema = json.load(f)

uiclient = UiForm()

response = uiclient.documents.extractions.parse(
    json_schema = json_schema,
    document="booking_confirmation.jpg",
    model="gpt-4.1-nano",
    n_consensus=10  # This will run and combine the results of 10 calls to the same LLM
)

Webhooks

UiForm uses HTTPS to send webhook events to your app as a JSON payload representing a WebhookRequest object.

The WebhookRequest object is a Pydantic model that contains the following fields:

  • completion: The parsed chat completion object, containing the extracted data.
  • user: The user email address.
  • file_payload: The file payload object, containing the file name, url and other metadata.
  • metadata: Some additional metadata.
from openai.types.chat.parsed_chat_completion import ParsedChatCompletion

class MIMEData(BaseModel):
    filename: str = Field(description="The filename of the file", examples=["file.pdf", "image.png", "data.txt"])
    url: str = Field(description="The URL of the file in base64 format", examples=["data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA..."])
    ##... other fields

class WebhookRequest(BaseModel):
    completion: ParsedChatCompletion
    user: Optional[EmailStr] = None
    file_payload: MIMEData
    metadata: Optional[dict[str, Any]] = None

To start receiving webhook events in your app:

  • Create a webhook endpoint handler to receive event data POST requests.
  • Create a new deployment sending data to your webhook endpoint.
  • Test your webhook endpoint handler locally using the UiForm SDK.
  • Secure your webhook endpoint.

Create a webhook endpoint handler

Set up an HTTPS endpoint function that can accept webhook requests with a POST method.

Set up your endpoint function so that it:

  • Handles POST requests with a JSON payload consisting of an event object.
  • Quickly returns a successful status code (2xx) prior to any complex logic that might cause a timeout.
import os

from fastapi import FastAPI, Request, Response, HTTPException
from uiform import UiForm

uiclient = UiForm()
app = FastAPI()

@app.post("/webhook")
async def webhook_handler(request: Request):

    payload = await request.body()

    # Decode and parse the webhook request from the verified payload
    json_data = json.loads(payload.decode('utf-8'))
    webhook_request = WebhookRequest.model_validate(json_data)

    invoice_object = Invoice.model_validate_json(webhook_request.completion.choices[0].message.content or "{}") # The parsed object is the same Invoice object as the one you defined in the Pydantic model
    print("📬 Webhook received:", invoice_object)
    return {"status": "success", "data": invoice_object}


# To run the FastAPI app locally, use the command:
# uvicorn your_module_name:app --reload
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Create a new deployment sending data to your webhook endpoint

Let’s create a new processor and automation to send data to your webhook endpoint.

First, create a processor:

class Invoice(BaseModel):
    amount: int
    currency: str
    customer_email: EmailStr


from uiform import UiForm

uiclient = UiForm()

# Step 1: Create a processor
processor = uiclient.processors.create(
    name="Invoice Processor",
    model="gpt-4.1",
    json_schema=Invoice.model_json_schema(),
)

# Step 2: Create an automation and attach it to the processor
extraction_link = uiclient.processors.automations.links.create(
    name="Invoices",
    processor_id=processor.id,
    webhook_url="https://your_server.com/invoices/webhook",
)

Test your webhook endpoint handler locally using the UiForm SDK

Before you go-live with your webhook endpoint function, we recommend that you test your application integration.

You can do so by sending a test request to your webhook endpoint using the UiForm SDK.

from uiform import UiForm

uiclient = UiForm()
link_log = uiclient.processors.automations.links.test_document_upload(
    link_id=extraction_link.id,
    document = "invoice.pdf"
)

Secure your webhook endpoint

You need to secure your integration by making sure your handler verifies that all webhook requests are generated by UiForm.

You perform the verification by providing the event payload, the UiForm-Signature header, and the endpoint’s secret. If verification fails, you get an error.

Important: Make sure to replace "your_webhooks_secret_here" with the actual secret from your UiForm dashboard.

import os

from fastapi import FastAPI, Request, Response, HTTPException
from uiform import UiForm

uiclient = UiForm()
app = FastAPI()

@app.post("/webhook")
async def webhook_handler(request: Request):

    payload = await request.body()

    #########################################################
    # SIGNATURE VERIFICATION
    #########################################################
    try:
        # Read the signature from the request headers
        
        signature_header = request.headers.get("UiForm-Signature")

        if not signature_header:
            raise HTTPException(status_code=400, detail="Missing UiForm-Signature header")

        # Verify the signature and process the event
        uiclient.processors.automations.verify_event(
            event_body=payload,
            event_signature=signature_header,
            secret=os.getenv("WEBHOOKS_SECRET"),
        )

        return Response(status_code=200)
    except Exception as e:
        # Handle errors (optional: log the error)
        return Response(status_code=400, content=f"Webhook error: {str(e)}")

    ########################################################   
    # END OF SIGNATURE VERIFICATION
    #########################################################

    # Decode and parse the webhook request from the verified payload
    json_data = json.loads(payload.decode('utf-8'))
    webhook_request = WebhookRequest.model_validate(json_data)

    #Then continue with your webhook handler logic .....
    invoice_object = Invoice.model_validate_json(webhook_request.completion.choices[0].message.content or "{}") # The parsed object is the same Invoice object as the one you defined in the Pydantic model
    print("📬 Webhook received:", invoice_object)
    return {"status": "success", "data": invoice_object}


Vulnerability

When you set up a webhook, you provide an HTTP endpoint on your server for UiForm to send data to. If this endpoint is not secured (i.e., it accepts unauthenticated POST requests from anywhere), it essentially becomes a public door into your system. Any actor could attempt to call this URL and send fake data. This is inherently dangerous: a malicious party might send forged webhook requests that masquerade as UiForm, but contain bogus or harmful data. Without verification, your server might accept these fake events and perform unintended actions. In short, an open webhook endpoint without authentication is highly vulnerable to abuse.

Mitigation

To secure webhook deliveries, UiForm employs a signature verification mechanism using an HMAC-like scheme (similar to the approach used by Stripe, WorkOS, and other providers). HMAC stands for Hash-Based Message Authentication Code, a method that uses a secret key to create a cryptographic signature for each message. In fact, HMAC signing is by far the most popular strategy for webhook security (used in ~65% of webhook implementations), because it ensures that only the trusted source (with knowledge of the secret) could have sent the request.

How it works: UiForm and your application share a webhook secret (a random string known only to UiForm and you). This secret is available in your UiForm dashboard (often labeled as WEBHOOKS_SECRET). UiForm uses this secret to include a special signature header with every webhook request:

  • Signature Generation (UiForm): When UiForm prepares to send a webhook, it computes an HMAC-SHA256 signature of the webhook’s payload using your secret key, and includes this signature in the request headers (specifically in the UiForm-Signature header).
  • Signature Verification (Your Server): When your endpoint receives the webhook, your code should perform the same HMAC-SHA256 computation on the request body using the shared secret. Then, compare your computed signature to the value in the UiForm-Signature header.
  • Comparison Result: If the two signatures match, it means the request truly came from UiForm (since only UiForm knows the secret) and that the payload was not altered in transit. You can then safely trust and process the webhook. If the signatures do not match, the request is illegitimate – either coming from an impostor or corrupted – and you should reject it (e.g. respond with an HTTP 400 Bad Request or 401 Unauthorized).

By using this HMAC signature validation, we achieve both authentication and integrity for webhook messages: only the genuine sender (UiForm) can produce a matching signature, and any change to the data would break the signature verification. In other words, a valid UiForm-Signature proves that the webhook content is exactly what UiForm sent and has not been tampered with.

Code Snippets


Here are some code snippets for different frameworks.

import os

from fastapi import FastAPI, Request, Response, HTTPException
from uiform import UiForm

uiclient = UiForm()
app = FastAPI()

@app.post("/webhook")
async def webhook_handler(request: Request):
    try:
        # Read payload and signature from request
        payload = await request.body()
        signature_header = request.headers.get("UiForm-Signature")

        if not signature_header:
            raise HTTPException(status_code=400, detail="Missing UiForm-Signature header")

        # Verify the signature and process the event
        uiclient.processors.automations.verify_event(
            event_body=payload,
            event_signature=signature_header,
            secret=os.getenv("WEBHOOKS_SECRET"),
        )

        return Response(status_code=200)
    except Exception as e:
        # Handle errors (optional: log the error)
        return Response(status_code=400, content=f"Webhook error: {str(e)}")