# What is Penbox Agent
Source: https://docs.penbox.io/agent/introduction
The AI assistant built into Penbox
Penbox Agent is the AI assistant built into Penbox. You describe what you want, and it applies the change.
Penbox Agent builds your Form Templates, Case Templates, and Document Intelligence Templates. It also works on your live cases, all from the same chat panel, with capabilities that adapt to the page you have open.
Every outbound message Penbox Agent creates is a draft. Nothing leaves your
workspace until you approve it.
## A builder and a runner
Penbox Agent does two distinct things.
### The builder
In the Form Template, Case Template, and Document Intelligence Template editors, Penbox Agent is a builder. You describe what to build or change, and Penbox Agent does it.
This is where Penbox Agent handles complex Penbox logic for you:
* **Conditional logic.** "Show the Vehicle step only when the user picks Car on step 1 and when the estimated damage amount is greater than 1000." Penbox Agent writes the visibility expression.
* **Flow variables.** "Compute the full name from given name and family name." Penbox Agent defines and wires the variable.
* **Multilingual content.** Penbox Agent translates every visible string and updates the form's languages list.
* **AI extraction.** Penbox Agent lists data points to extract from an uploaded document and binds them to form fields.
* **Building from a document.** Drop a paper form or claim report. Penbox Agent reproduces it end to end.
Typical workflow: open the template, describe what you want, watch each change land live, iterate.
Penbox Agent, thinks with you, and builds for you. He handles all the technical complexity.
Give Penbox Agent a business brief, for example:
It designs the form, computes the pricing variables, wires the conditional logic, and confirms what it built:
The quote step uses PenScript expressions to handle the math. Your contact sees a polished, branded breakdown:
What Penbox Agent handles for you
→
What your contact sees
### The runner
On a running case, Penbox Agent is your operator's assistant: it reads the timeline and the data, answers questions, drafts the next message, and moves the case forward.
This is where Penbox Agent answers operational questions:
* **What's the state?** "Summarize this case." "What did the contact say last?" "What's the IBAN we received?"
* **What's next?** "Draft a reminder to the main contact about the missing photos."
* **Move the case.** "Move to 'In review'." "We're waiting for the contact now."
Typical workflow: open the case, ask what you need to know, get a draft for the next move, review and send.
## Setup
How you open the chat depends on the surface:
* **In a case or a Case Template.** Click **Ask Penbox** in the top right. The button turns blue and the chat opens on the right.
* **In the Form Template editor.** Use the **Assistant / Preview** toggle in the top right. Assistant opens the chat; Preview hides it and shows the form as a contact would see it.
* **In a Document Intelligence Template.** The chat is always open.
Penbox Agent does not edit Email Templates or SMS Templates. Those stay manual.
Build forms: steps, fields, conditional logic, translations, signatures, automations.
Design recurring case workflows: data schema, contacts, steps, statuses,
automations.
Configure extraction fields for documents.
Answer questions, set values, draft the next step, move the case forward.
## Essentials
### Reverting changes
Penbox Agent records each change it makes as a separate entry in the chat. A simple request produces one entry. A complex request, like building a form from scratch, produces many. Penbox Agent breaks the work into smaller steps and each step gets its own entry.
The entry header names the surface and how many attributes the step touched. The label format depends on the surface:
* Form Template editor: **Adapted form ...**
* Case Template editor: **Adapted template ...**
* Live case: **Updated case ...**
The count after the label reflects the number of attributes touched. A step may touch a single attribute (`1 change`) or many (`7 changes`).
Hover an entry to reveal the revert arrow. Click it to undo, click again to reapply.
Expand an entry to see its individual changes, each marked with a green checkmark once applied. Hover an individual change to:
* Highlight the matching element in the editor (purple outline).
* Reveal a revert arrow for that single change. Click to undo just that one.
To wipe everything Penbox Agent built in one go, ask it in plain language: "clear
the form", "empty the case template". Faster than clicking revert on every
entry.
In a Document Intelligence Template, the revert arrow does not appear in the
chat. Ask Penbox Agent in plain language ("undo that last change") and it will
revert.
### Chat history
Penbox Agent keeps every conversation you start. Open the chat panel and you see your previous conversations on this artifact, each with an auto-generated title summarizing the first message.
* History is per artifact. A Form Template, a Case Template, a Document Intelligence Template, and a Case each have their own conversation list.
* History is private. Your colleagues have their own conversation lists, even on the same artifact.
* Open a previous conversation to resume it. Penbox Agent picks up where you left off.
* Delete a conversation when you no longer need it.
### Working with files
Drop or attach a PDF/image into the chat. Penbox Agent reads it and builds the matching artifact.
* Drop a paper form into the Form Template editor. Penbox Agent reproduces it as a Penbox form with every field, every section, and the right types.
* Drop a claim report or intake form into the Case Template editor. Penbox Agent generates a Case Template with the matching data schema, contacts, and workflow steps.
Send the file with no message and Penbox Agent treats it as a request to build the artifact from scratch. It announces a short plan, then executes.
Files only exist on the turn you send them. Penbox Agent has no memory of a file
from an earlier message. Reference an older document and it will ask you to
re-upload.
### Languages
Penbox Agent answers in the language you write in, or in the locale of the template or case you have open. Multilingual Form Templates work end to end: Penbox Agent translates form content into French, Dutch, English, German, and other languages.
## What Penbox Agent does not do
* Does not send messages. On live cases, every email, SMS, or form is a draft.
* Does not trigger automations directly. Automations run on their own conditions.
* Does not edit artifacts other than the one you have open.
* Does not guess. Ambiguous requests get a clarifying question.
***
## Next Steps
Build templates with Penbox Agent
Work on running cases with Penbox Agent
Build forms with Penbox Agent
Design case workflows with Penbox Agent
# Penbox Agent on Live Cases
Source: https://docs.penbox.io/agent/live-cases
Understand a case, edit its data, draft the next step
Penbox Agent works on the case you have open: it reads the timeline and the current data, answers questions, applies changes, and drafts the next message.
Penbox Agent does not send messages and it does not modify the Case Template behind the case.
Penbox Agent on a live case operates on the case only. Changes here do not affect
the Case Template or other cases created from it.
## What you can do
**Read and explain**
* Summarize the case end to end or focus on a specific moment.
* Recap a conversation: who said what, what was promised, what is pending.
* Find a value: "What's the IBAN we received?", "When did the contact reply?", "What was decided on the deductible?"
* Pull a value out of an email already received in the case.
**Edit case data**
* Set or clear values on fields in the case schema. Each update lands in the timeline and may trigger automations.
* Group related updates ("set the IBAN to X and the account holder to Y") into a single batch.
**Edit the case structure (this case only)**
* Add a new field to the case schema.
* Add a section.
* Add a contact role.
These changes affect only the open case. The Case Template stays untouched.
**Draft the next step**
* Create a draft email, SMS, or form step.
* The draft uses real case data: names, references, values. No placeholders.
* Review the draft in the timeline. Send it manually.
**Move the case forward**
* Change the case status (picking from statuses defined on the Case Template).
* Set the waiting-for indicator: `owner` (the team), `contact` (an external party), or `none`.
**Manage reminders on form steps**
* Add or edit reminders on a form step that has not been sent yet.
* Schedule an additional reminder on a form step already sent.
## Drafts vs sending
Penbox Agent never sends an email, SMS, or form. Every outbound message is a draft.
1. You ask Penbox Agent to draft a message: "draft a reminder to Marie about the missing photos."
2. Penbox Agent creates the step on the case using the real names, references, and values.
3. You open the draft in the timeline, adjust if needed, and click send.
Even if you say "send the reminder", Penbox Agent creates the draft and stops.
Sending is a human click away.
## Example prompts
> "Summarize this case in five lines."
> "What did the contact say in their last email?"
> "Set the contract amount to 12 500 €."
> "Add a 'broker' contact role to this case."
> "Draft a reminder email to the main contact asking for the missing photos of the damage."
> "Move this case to 'In review'."
> "We're waiting for the contact now."
> "Add a reminder in three days on the document request form."
## What Penbox Agent does not do here
* Does not send messages. Outbound is always a draft.
* Does not trigger automations. Automations run on their own conditions.
* Does not create internal form steps or signature steps. Those step types stay manual.
* Does not edit the Case Template behind the case. Open the Case Template in Studio for that.
* Does not invent data. If a value is not in the case, Penbox Agent says so.
***
## Next Steps
Cases in Penbox
Design the templates behind your cases
Penbox Agent inside Form Templates
What Penbox Agent does across Penbox
# Penbox Agent for Case Templates
Source: https://docs.penbox.io/agent/templates/case-templates
Design recurring case workflows with Penbox Agent
A Case Template is the blueprint for a recurring case type: data schema, contacts, workflow steps, statuses, automations. Penbox Agent designs every part of it.
## What you can do
**Data schema**
* Add, rename, remove, or reorder fields.
* Set the field type (`text`, `number`, `date`, `file`, `choices`, `checkbox`).
* Set visibility (`always-visible`, `hide-when-empty`, `always-hide`).
* Configure type-specific options (placeholder, hint, min/max, accepted file types, choice list).
**Sections**
* Group related fields into named sections.
* Reorder or remove sections. Toggle section visibility.
**Contact roles**
* Add secondary contacts with custom roles: broker, expert, billing contact, witness.
* Rename or remove secondary contacts. The main contact is required and cannot be removed.
**Workflow steps**
Penbox Agent adds and edits three step types: email, SMS, and form. Internal form steps and signature steps stay manual for now.
* **Email.** A one-way email to a contact: confirmation, status update, internal notification.
* **SMS.** A short text message to a contact.
* **Form.** A request sent to a contact to collect data. Built inline on the step, or attached from an existing Form Template.
For each step, Penbox Agent configures the title, the recipient, the subject and body (static HTML or an AI-generated body that adapts to the case data at send time), attachments, and conditional execution.
**Custom statuses**
Define the statuses your case moves through (Pending, In Review, Awaiting Documents, Closed) and configure automatic transitions: move from one status to another when a condition is met.
**Automations**
Configure webhooks and email automations triggered by step completion, status changes, or data updates.
**AI extraction**
Configure AI extraction on file fields. Two mechanisms:
* **Inline extraction.** Defined directly on the file field. Best for ad-hoc extraction.
* **Existing Document Intelligence Template.** Penbox Agent attaches a reusable template from your workspace and maps its fields to the case schema.
## Inline forms vs reusable Form Templates
Two options when you add a form step.
* **Inline form.** Penbox Agent defines the form fields directly on the step. Best when the form is specific to this Case Template.
* **Attach an existing Form Template.** Penbox Agent searches your workspace by name, asks you to confirm, and attaches the right one. Best when several Case Templates reuse the same form.
Describe what the form should ask for ("add a step that requests proof of
identity and a recent utility bill") and Penbox Agent builds an inline form. Ask
explicitly to "use the KYC form" or "attach our onboarding form" to reuse an
existing Form Template.
## Auto-syncing form data into the schema
When Penbox Agent adds a form step, it proposes to add the form's fields to the case schema too. The schema stays in sync with the data the case will collect: other steps can pre-fill from those fields, automations can react to them, and your team sees what data is expected before the form comes back.
Accept the suggestion, modify it, or skip it.
## Example prompts
> "Add a billing contact called Contact facturation."
> "Add an email step that confirms the case opening to the main contact."
> "Add a form step that requests an ID card and a proof of residence. Configure AI extraction on the ID card."
> "Create a status 'Awaiting documents' and transition to it automatically when the document field is empty after the form was sent."
> "Add an automation that posts the case to our CRM when the status becomes Closed."
> "Build a Case Template from this document."
## What Penbox Agent does not do here
* Does not create internal form steps or signature steps. Those step types stay manual in the Case Template editor.
* Does not edit permissions or sender integrations (email sender, signature provider). Those stay in the workspace settings.
* Does not edit a Form Template attached to a step. Open the Form Template and use Penbox Agent there.
* Does not edit settings outside the data schema, contacts, workflow steps, statuses, and automations of the Case Template.
***
## Next Steps
Case Templates in Penbox
Penbox Agent inside Form Templates
Configure extraction templates
Penbox Agent on running cases
# Penbox Agent for Document Intelligence Templates
Source: https://docs.penbox.io/agent/templates/document-intelligence
Define what to extract from documents
A Document Intelligence Template tells Penbox what to extract from a document: a list of fields, each with a type and an optional hint. Penbox Agent builds and refines these templates.
## What you can do
**Extraction fields**
* Add, rename, remove, or reorder fields.
* Set the field type: `text`, `number`, `date`, or `list` (a repeatable group of sub-fields, like line items on an invoice).
* Add a hint. A short instruction that disambiguates the extraction ("use the issue date, not the expiry date" or "the number printed at the bottom of the card").
* For list fields, define the sub-fields the extraction produces for each item.
**Keys**
Penbox Agent generates a snake\_case `key` for each field when you do not provide one. The key is the identifier you reference elsewhere in Penbox, for example from a Case Template that uses this Document Intelligence Template on a file field.
## Example prompts
> "Add the fields needed to extract data from an ID card."
> "Add a list field `line_items` with description, quantity, unit price, and total."
> "Add a hint to the issue date field so it picks the issuance date, not the expiry date."
> "Rename `invoice_number` to `ref_number`."
## Where these templates are used
Once a Document Intelligence Template exists in your workspace, attach it to a file field inside a Case Template. Penbox Agent recognizes "use the workspace template" or "use the invoices template" and wires it up. See [Penbox Agent for Case Templates](/agent/templates/case-templates#ai-extraction).
## What Penbox Agent does not do here
* Does not run the extraction on a document for you. Testing happens in the Document Intelligence Template's test interface.
* Does not invent fields you did not ask for. When the document type is ambiguous, Penbox Agent asks which fields you want.
***
## Next Steps
Use a Document Intelligence Template inside a Case Template
Configure inline AI extraction on a form file field
What Penbox Agent does across Penbox
Penbox Agent on running cases
# Penbox Agent for Form Templates
Source: https://docs.penbox.io/agent/templates/form-templates
Build and edit forms by describing what you want
Penbox Agent edits the Form Template you have open. You describe a change, and it applies it.
## What you can do
**Form structure**
* Add, remove, rename, or reorder steps.
* Add, remove, or edit elements inside a step.
* Set conditional visibility on a step, a section, or a field.
* Mark fields required. Set placeholders, hints, and validation (min/max, length, format).
**Field types**
Penbox Agent knows every Penbox field type: text, number, date, choices (single or multiple), file upload, signature, phone, country, currency, IBAN, checkbox, toggle, autocomplete, rating, and display elements (paragraph, card, image, download).
**Welcome and ending pages**
Configure the welcome screen (title, intro message, custom image, privacy policy, help button) and the ending screen (confirmation message, image, redirect URL).
**Flow variables**
Define reusable computed values once and reference them anywhere in the form. Useful for derived values (`{full_name} = {user.given_name} {user.family_name}`) or shared labels.
**Sender-filled Options**
Configure the secondary form the sender fills in when launching the main form (case type, reference, logo). Values entered in Options become available across the main form and in conditional logic.
**Signatures**
Add signature elements with the right method: handwritten, SMS code, email code, ID-card reader, Itsme. Configure additional signers, legal notices, and signature placement on documents.
**AI extraction on file fields**
List the data points the contact's document should yield (last name, first name, ID number, expiry date). Penbox Agent wires the extraction to the matching form fields.
**Invitations and reminders**
Configure form delivery and follow-up reminder cadence.
**Automations on the form**
Trigger an email or HTTP call on form lifecycle events: opened, updated, submitted, declined, or expired.
## Translations
Ask Penbox Agent to translate the open form into French, Dutch, German, English, or any combination, and the entire form is multilingual in under a minute. Every visible string the contact sees gets translated: step titles, field labels, hints, placeholders, choice options, validation messages, welcome and ending pages. The form's available languages list is updated in the same pass.
Done manually, this means filling every element's translation slot by hand, hours of work for each language. With Penbox Agent, it is a one-line prompt.
Add a language later by asking. Penbox Agent translates what is missing without touching strings already translated.
## Example prompts
> "Add a step that asks for the IBAN and the account holder's name."
> "Make the email field required and add a placeholder."
> "Show the Vehicle step only when the user picks Car on step 1."
> "Translate this form to French and Dutch."
> "Add a signature step using Itsme."
> "On the ID card field, extract last name, first name, date of birth, ID number, and expiry date."
> "Build a form from this document."
## Smart suggestions
Click the lightbulb icon in the chat to turn smart suggestions on. With suggestions enabled, Penbox Agent ends each reply with one improvement to consider, like AI extraction on a file field that does not have it, or an obvious required field not marked required. Reply "yes" and Penbox Agent applies it.
Suggestions are grounded in the form you have open, never generic advice.
## What Penbox Agent does not do here
* Does not edit forms other than the one you have open.
* Does not send the form. You publish and share it from the form's invitation settings.
* Does not invent unsupported syntax. If something cannot be done in PenScript, Penbox Agent says so.
***
## Next Steps
Form Templates in Penbox
Penbox Agent inside Case Templates
Configure AI extraction templates
Penbox Agent on running cases
# Penbox Agent for Templates
Source: https://docs.penbox.io/agent/templates/introduction
Build the templates that power your workspace
Templates are the reusable artifacts that drive your operations. Penbox Agent edits three of them: Form Templates, Case Templates, and Document Intelligence Templates.
Penbox also supports Email Templates and SMS Templates. Penbox Agent does not
edit those. They need to be manually edited.
## The three template types
The form your contacts fill in: steps, fields, pages, translations, signatures, AI extraction, invitations.
The blueprint for a recurring workflow: data schema, contacts, workflow steps,
statuses, automations.
The extraction definition for documents.
## How template editing works
Create or open a template, open Penbox Agent, and describe what you want.
1. You describe a change, or attach a PDF or image.
2. Penbox Agent announces a short plan.
3. Penbox Agent applies the change incrementally. You see each change appear live in the editor.
4. Penbox Agent confirms what changed. In the Form Template editor, it can add one actionable suggestion to improve the form if you click on the lightbulb icon.
> "Add a step that asks for the IBAN and the account holder's name."
> "Translate this form to French and Dutch."
## Building from a document
Drop a document. Penbox Agent reads it and scaffolds the template.
* Drop a paper form into the Form Template editor. Penbox Agent reproduces it as a Penbox form.
* Drop a claim report, intake form, or contract into the Case Template editor. Penbox Agent generates the matching data schema, contacts, and workflow steps.
Send the file with no message and Penbox Agent builds end to end, announcing a short plan before executing.
Use a clean, representative example. Penbox Agent infers field types,
conditional sections, and required information from what it sees.
## What carries across all three
* Penbox Agent reads the live template before every change. Edits made in the UI outside the chat are picked up automatically.
* Penbox Agent asks a short clarifying question instead of guessing.
* Large requests are split into many small calls. You see each change land and you can stop the agent at any point.
* Every change is reversible. See [Reverting changes](/agent/introduction#reverting-changes) for the per-surface details.
***
## Next Steps
What Penbox Agent does inside a Form Template
What Penbox Agent does inside a Case Template
Configure extraction fields with Penbox Agent
Penbox Agent on running cases
# Penbox for AI agents
Source: https://docs.penbox.io/agents/introduction
Drive Penbox from Claude Code, Claude, Codex or any MCP client.
Penbox exposes an MCP server. An AI agent connected to it can open cases, follow them, build case templates and form templates, read form responses, and reach the rest of the public API. It works from Claude Code, claude.ai, Claude Desktop, Codex and any MCP client.
Two commands for Claude Code. One URL for claude.ai. A zip for Codex.
## What you get
### The MCP server
The server lives at `https://connect.penbox.io/mcp`. It speaks business vocabulary: case, case template, form, form template. Every tool runs as the signed-in user, with that user's rights and nothing more.
| Area | Tools |
| - | - |
| Workspace | `list_workspaces` |
| Cases | `list_cases`, `get_case`, `create_case`, `assign_contact`, `update_case`, `update_case_status`, `update_case_data`, `update_case_waiting_for`, `update_draft`, `add_step_reminder`, `start_case_step` |
| Tasks | `list_tasks`, `create_task`, `update_task` |
| Case templates | `list_case_templates`, `get_case_template`, `create_case_template`, `update_case_template` |
| Form templates | `list_form_templates`, `get_form_template`, `create_form_template`, `update_form_template` |
| Forms | `list_forms`, `get_form`, `get_form_response` |
| Knowledge | `get_skill`: the reference guides an agent loads before building a template |
| Public API | `penbox_api_search`, `penbox_api_details`, `penbox_api_call`: the rest of the [API](/api-reference/introduction) |
`start_case_step` is the only tool that sends something for real: an email, an SMS or a form goes out to the contact. Every other write stays inside Penbox.
### The skills
A plugin adds five skills on top of the server. A skill is a short playbook the agent loads when the task matches. It teaches the order of operations, not the tools. See [Skills](/agents/skills).
## Two ways to sign in
* **As a user.** The default. The agent opens your browser once. You sign in to Penbox and approve. The agent then acts as you. What you cannot do in Penbox, it cannot do either.
* **With a workspace API token.** For a server-side agent with no human at the keyboard. Create the token in your [workspace settings](/workspace/settings/api) and use it as the bearer on `https://connect.penbox.io/mcp`. The agent is then scoped to that workspace.
## Where it runs
| Surface | Path |
| - | - |
| Claude Code | Plugin: server and skills |
| claude.ai, Claude Desktop | Custom connector: server only |
| Claude organization | Plugin synced to every member by an admin |
| Codex | Plugin from the zip: server and skills |
| Any MCP client | Server only |
## Next
Every install path, and prompts to start with.
What each skill does and when it triggers.
Rights, data, and what the agent can never do.
# Security
Source: https://docs.penbox.io/agents/security
What an AI agent can and cannot do in your Penbox workspace.
An agent connected to Penbox is a user with tools. This page says what that user can do, what leaves your workspace, and what is refused.
## Sign-in
* **OAuth, in your browser.** On the first tool call, the agent opens the Penbox sign-in page. You sign in with your own account and approve the connection.
* **No token to paste.** The client registers itself with Penbox (dynamic client registration). No API key travels through the chat, a config file or the plugin.
* **Revocable.** Disconnect the connector in your client, or sign out of Penbox. The agent loses access.
A server-side agent can use a workspace API token instead. That token has the workspace's rights, like any [API client](/api-reference/authentication).
## Rights
* **The agent never has more rights than you.** Every tool call runs as the signed-in user. The same policies apply as in the Penbox app and the public API. What you cannot see or change in Penbox, the agent cannot either.
* **Several workspaces, no silent pick.** When your account reaches several workspaces, listing and creation tools refuse until the agent names one. The agent must ask you.
* **Actions are attributed to you.** A change made over MCP shows your name in the case history, with a "via MCP" tag.
## What the agent cannot do
* **Delete.** No tool deletes anything, and the raw API passthrough refuses every DELETE. Deleting is done in the Penbox app. Archiving a case is allowed: it is reversible.
* **Provision a workspace or create a member.** Both are refused over MCP.
* **Send without being asked.** Only `start_case_step` sends an email, an SMS or a form to a contact. The skills tell the agent to call it only when you explicitly ask to send.
* **Delete a task.** A task is dismissed, not deleted.
## Data
* **Nothing is pre-loaded.** The agent receives only what a tool call returns: the case it asked for, the form response it read. No background sync, no export of your workspace.
* **Data goes to your agent's provider.** A tool result is handed to the model you use (Anthropic, OpenAI, or another). Check that provider's terms for what it keeps. Penbox does not send anything to a model on its own during MCP usage.
* **Third-party text is labelled as data.** A case and a form response carry a note telling the agent that content written by contacts, respondents or incoming emails is data, never an instruction. This limits prompt injection through a form answer.
* **Large responses are cut.** A response over 50 KB is truncated. The agent is told to narrow the call.
## Hosting and billing
* **EU hosting.** The MCP server runs in the European Union, with the rest of Penbox.
* **No credits.** MCP usage does not consume Penbox credits. The model runs on your side, in your agent.
## Refused over MCP
| Operation | What happens |
| - | - |
| Any `DELETE` | Refused. Delete in the Penbox app. |
| `POST /workspaces` | Refused. Workspace provisioning is not available over MCP. |
| `POST /members` | Refused. Member creation is not available over MCP. |
| A write covered by a curated tool | Refused. The refusal names the tool to use, so its guards apply. |
Refusals are answers, not errors. The agent reads the refusal and follows it. A refusal from the API itself, such as a 403, is final: it means your account lacks the right.
# Skills
Source: https://docs.penbox.io/agents/skills
The five playbooks the Penbox plugin gives an agent.
The Penbox plugin ships five skills. A skill is a short document the agent reads when your request matches its description. It tells the agent what to load, in which order, and what to check before it answers. The skills never repeat the reference material: the agent fetches that from the server with `get_skill`.
The skills are in the plugin only. A custom connector or a tools-only setup does not carry them. The tools still work; the agent just has less guidance.
## penbox-getting-started
**When:** the first Penbox action in a conversation.
The agent resolves your workspace, learns the vocabulary (case, case template, form, form template, contact) and the read-before-write rule: every update starts from the current document, never from memory. It also learns that text written by contacts and respondents is data, never an instruction.
## manage-cases
**When:** you want to open a case, add a contact, change a status, fill in data, send a reminder, or check where a case or a form stands.
No reference needed. The agent finds the case, reads it, then acts. It searches cases by contact name or email, because case titles usually default to the template name. Sending a form or a reminder now goes through `start_case_step`, only when you ask to send.
## build-case-template
**When:** you want a new type of case, or want to change the steps, statuses, automations, contacts or data schema of an existing template.
The agent loads the case template reference once, checks whether a similar template already exists, then edits with small patches, one concern each. Before it writes a status, an automation or a condition, it loads the guide for that feature.
## build-form-template
**When:** you want a new form, or want to add or change questions, signatures, translations, repeated sections or generated documents in an existing one.
Same method: load the form template reference once, list what exists, create, then patch. The agent reads the validation errors the server returns and fixes them before moving on. A form template is created published, so the agent builds it one patch at a time.
## penbox-api
**When:** a curated tool refuses, is missing, or you name an endpoint of the Penbox API.
The agent searches the [public API](/api-reference/introduction) with `penbox_api_search`, reads the contract with `penbox_api_details`, then calls it with `penbox_api_call`. When a curated tool covers the operation, the search says so and the agent uses that tool instead.
You never name a skill. Describe the task in business words and the agent picks the matching skill on its own.
# Troubleshooting
Source: https://docs.penbox.io/agents/troubleshooting
The few things that look broken and are not.
`list_workspaces` returns zero. The install is fine. Your Penbox account is not a member of any workspace yet.
Ask your workspace administrator to [add you as a member](/workspace/settings/members), then try again. No reinstall needed.
A plugin installed during a session stays invisible to that session.
Open a new session. In Claude Code you can also run `/reload-plugins` in the current one.
Codex asks you to approve the filesystem write at install, and again the first time a tool runs. `codex exec` cannot answer those prompts.
Run the install and the first tool call in an interactive Codex session. After that, `codex exec` works.
Your client picks up a new version on its next launch, and only when the declared version moved.
Restart the client. If the version is unchanged, there is nothing new to fetch.
Sign-in happens on the first Penbox tool call, not at install time.
Ask the agent to list your workspaces. That first call triggers the OAuth flow.
Write to [support@penbox.io](mailto:support@penbox.io) with the tool name and the exact refusal message.
# Advanced Filters
Source: https://docs.penbox.io/api-reference/advanced-filters
Comprehensive guide to filtering API resources with query parameters and JSON filters
The Penbox API provides two powerful filtering methods to query and retrieve exactly the data you need. Both methods are available on `GET` (list) and `GET /count` endpoints.
## Two Filtering Methods
### Method 1: Direct Query Parameters
The simplest way to filter is by adding query parameters directly to the URL:
```bash theme={null}
GET /v1/cases?workspace[id]=abc-123&owner[email]=john@acme.com&status=pending
```
**Benefits:**
* Simple and readable
* Easy to construct manually
* Good for basic filtering needs
**Example:**
```javascript theme={null}
// Filter cases by workspace and owner
const params = new URLSearchParams({
'workspace[id]': workspaceId,
'owner[email]': 'sales@acme.com',
'archived': 'false'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
### Method 2: Advanced JSON Filter
For complex queries, use the `filter` parameter with a JSON object containing operators:
```bash theme={null}
GET /v1/cases?filter={"attributes":{"created_at":{"$gt":"2024-01-01"}}}
```
**Benefits:**
* Powerful operator-based filtering
* Support for complex conditions
* Filter on attributes and relationships
* Combine multiple criteria with operators
**Example:**
```javascript theme={null}
const filter = {
attributes: {
created_at: { $gt: '2024-01-01T00:00:00Z' },
status: { $ne: 'draft' }
},
relationships: {
owner: {
attributes: {
email: { $eq: 'sales@acme.com' }
}
}
}
};
const response = await fetch(
`https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
## Filter Operators
When using the JSON filter method, you can use the following operators:
| Operator | Description | Example |
| - | - | - |
| `$eq` | Equals | `{ status: { $eq: 'pending' } }` |
| `$ne` | Not equals | `{ status: { $ne: 'draft' } }` |
| `$gt` | Greater than | `{ created_at: { $gt: '2024-01-01' } }` |
| `$lt` | Less than | `{ created_at: { $lt: '2024-12-31' } }` |
| `$ge` | Greater than or equal to | `{ created_at: { $ge: '2024-01-01' } }` |
| `$le` | Less than or equal to | `{ created_at: { $le: '2024-12-31' } }` |
| `$like` | Pattern matching | `{ title: { $like: '%onboarding%' } }` |
| `$ilike` | Case-insensitive pattern matching | `{ title: { $ilike: '%Customer%' } }` |
The `$like` and `$ilike` operators support SQL-style wildcards: `%` matches any characters
Comparison operators (`$gt`, `$lt`, `$ge`, `$le`) can be used for numbers and dates.
## Filter Structure
The JSON filter object has two main sections:
### Attributes
Filter on the resource's direct attributes:
```json theme={null}
{
"attributes": {
"status": { "$eq": "pending" },
"archived_at": { "$eq": null },
"created_at": { "$gt": "2024-01-01T00:00:00Z" }
}
}
```
### Relationships
Filter on related resources:
```json theme={null}
{
"relationships": {
"owner": {
"id": { "$eq": "abc-123" }
},
"company": {
"attributes": {
"slug": { "$eq": "acme-corp" }
}
}
}
}
```
## Examples by Resource
### Filtering Cases
**Direct Parameters:**
```bash theme={null}
# Filter by template, workspace, and status
GET /v1/cases?template[id]=template-123&workspace[slug]=acme&parent_status=in_progress
```
**JSON Filter:**
```javascript theme={null}
// Cases created after a date with pending status
const filter = {
attributes: {
created_at: { $gt: '2024-01-01T00:00:00Z' },
parent_status: { $eq: 'pending' },
archived_at: { $eq: null }
},
relationships: {
workspace: {
attributes: {
slug: { $eq: 'acme-corp' }
}
}
}
};
```
### Filtering Case Templates
**Direct Parameters:**
```bash theme={null}
# Search by title and filter by workspace
GET /v1/case_templates?title=onboarding&workspace[slug]=acme&archived=false
```
**JSON Filter:**
```javascript theme={null}
// Templates with specific title pattern
const filter = {
attributes: {
is_template: { $eq: true },
title: { $ilike: '%customer%' },
archived_at: { $eq: null }
}
};
```
### Filtering Forms
**Direct Parameters:**
```bash theme={null}
# Filter by flow, user, and completion status
GET /v1/forms?flow[slug]=kyc&user[email]=john@example.com&completed=true
```
**JSON Filter:**
```javascript theme={null}
// Active forms with specific owner
const filter = {
attributes: {
active: { $eq: true },
completed: { $eq: false }
},
relationships: {
owner: {
attributes: {
email: { $eq: 'sales@acme.com' }
}
}
}
};
```
## Common Filtering Patterns
### Time-Based Filtering
```javascript theme={null}
// Cases created in the last 7 days
const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
const filter = {
attributes: {
created_at: { $gt: sevenDaysAgo }
}
};
```
### Status Filtering
```javascript theme={null}
// Active, non-archived cases in progress
const filter = {
attributes: {
parent_status: { $eq: 'in_progress' },
archived_at: { $eq: null }
}
};
```
### Text Search
```javascript theme={null}
// Case templates with "customer" in the title
const filter = {
attributes: {
title: { $ilike: '%customer%' }
}
};
```
### Multiple Criteria
```javascript theme={null}
// Complex filter combining multiple conditions
const filter = {
attributes: {
created_at: { $gt: '2024-01-01T00:00:00Z' },
parent_status: { $ne: 'draft' },
archived_at: { $eq: null }
},
relationships: {
workspace: {
attributes: {
slug: { $eq: 'acme-corp' }
}
},
owner: {
attributes: {
email: { $eq: 'sales@acme.com' }
}
}
}
};
```
## Available Filters by Endpoint
### Cases (`/v1/cases`)
**Direct Parameters:**
* `template[id]` - Filter by template UUID
* `status` - Custom status value
* `parent_status` - Parent status (draft, pending, in\_progress, closed)
* `archived` - Boolean (true/false)
* `waiting_for` - Who the case is waiting for (none, owner, contact)
* `reference` - Custom reference number
* `owner[id]` - Owner UUID
* `owner[email]` - Owner email
* `workspace[id]` - Workspace UUID
* `workspace[slug]` - Workspace slug
* `contact[given_name]` - Contact first name (exact match)
* `contact[family_name]` - Contact last name (exact match)
* `contact[email]` - Contact email (exact match)
* `contact[search]` - Free-text search over the contact — partial and accent-insensitive; see [List Cases](/api-reference/cases/list-cases)
### Case Templates (`/v1/case_templates`)
**Direct Parameters:**
* `title` - Search by title (partial match)
* `reference` - Custom reference
* `archived` - Boolean (true/false)
* `owner[id]` - Owner UUID
* `owner[email]` - Owner email
* `workspace[id]` - Workspace UUID
* `workspace[slug]` - Workspace slug
### Forms (`/v1/forms`)
**Direct Parameters:**
* `workspace[slug]` - Workspace slug
* `flow[slug]` - Form template slug (single value or array)
* `flow[customization]` - Template customization UUID
* `owner[email]` - Owner email
* `user[given_name]` - User first name
* `user[family_name]` - User last name
* `user[email]` - User email
* `user[phone]` - User phone
* `archived` - Boolean (true/false)
* `active` - Boolean (true/false)
* `completed` - Boolean (true/false)
* `processed` - Boolean (true/false)
## Best Practices
For basic filtering needs (workspace, status, owner), use direct query parameters. They're easier to read and debug.
When you need operators, date comparisons, or pattern matching, use the JSON filter method.
When passing JSON in the filter parameter, always use `encodeURIComponent()` to properly encode special characters.
Always use pagination when expecting many results to avoid timeouts and improve performance.
To check for null values, use `{ $eq: null }` or `{ $ne: null }` in your filter.
```javascript theme={null}
// Non-archived resources
{ archived_at: { $eq: null } }
```
When searching text fields, use `$ilike` instead of `$like` for case-insensitive matching.
## Real-World Examples
### Dashboard: Active Cases by Team Member
```javascript theme={null}
async function getActiveCases(ownerEmail) {
const filter = {
attributes: {
parent_status: { $eq: 'in_progress' },
archived_at: { $eq: null }
},
relationships: {
owner: {
attributes: {
email: { $eq: ownerEmail }
}
}
}
};
const response = await fetch(
`https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}&sort=-created_at`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
```
### Search Case Templates
```javascript theme={null}
async function searchTemplates(searchTerm) {
// Using direct parameter for simplicity
const params = new URLSearchParams({
title: searchTerm,
archived: 'false'
});
const response = await fetch(
`https://connect.penbox.io/v1/case_templates?${params}&sort=title`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
```
### Multi-Workspace Reporting
```javascript theme={null}
async function getCompletedCases(workspaceSlug, startDate, endDate) {
const filter = {
attributes: {
parent_status: { $eq: 'closed' },
created_at: { $gt: startDate, $lt: endDate }
},
relationships: {
workspace: {
attributes: {
slug: { $eq: workspaceSlug }
}
}
}
};
const response = await fetch(
`https://connect.penbox.io/v1/cases?filter=${encodeURIComponent(JSON.stringify(filter))}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
```
## Error Handling
Invalid filters return a `400 Bad Request` error:
```json theme={null}
{
"errors": [
{
"status": "400",
"title": "Invalid Filter",
"detail": "Invalid filter parameter format"
}
]
}
```
**Common Issues:**
* Invalid JSON format in filter parameter
* Unrecognized filter fields
* Type mismatches (e.g., string instead of boolean)
* Invalid operator usage
**Solution:**
```javascript theme={null}
try {
const response = await fetch(url, { headers });
if (!response.ok) {
const error = await response.json();
console.error('Filter error:', error);
return { data: [], meta: { total_count: 0 } };
}
return await response.json();
} catch (error) {
console.error('Request failed:', error);
return { data: [], meta: { total_count: 0 } };
}
```
## Next Steps
Control result size and order
Complete API specification with all available filters
Apply filters to the Cases endpoint
Filter forms with advanced queries
Search and filter case templates
# All API Endpoints
Source: https://docs.penbox.io/api-reference/all-endpoints
Complete reference of all API endpoints
## Quick Reference
This page provides a complete overview of all available API endpoints.
## Forms
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/forms` | [List forms with filtering and pagination](/api-reference/requests/list-requests) |
| `GET` | `/v1/forms/count` | [Count forms with optional grouping](/api-reference/requests/count-requests) |
| `POST` | `/v1/forms` | [Create a new form](/api-reference/requests/create-request) |
| `GET` | `/v1/forms/{id}` | [Get form details and responses](/api-reference/requests/get-request) |
| `PATCH` | `/v1/forms/{id}` | [Update form status or properties](/api-reference/requests/update-request) |
## Responses
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/responses/{id}` | [Get response details with attachments](/api-reference/responses/get-response) |
## Form Templates
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/form_templates` | [List available form templates](/api-reference/flows/list-flows) |
| `GET` | `/v1/form_templates/{slug}/options` | [Get template configuration options](/api-reference/flows/get-flow-options) |
| `GET` | `/v1/form_templates/{slug}/steps` | [Get template structure and fields](/api-reference/flows/get-flow-steps) |
## Cases
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/cases` | [List cases with filtering and pagination](/api-reference/cases/list-cases) |
| `GET` | `/v1/cases/count` | [Count cases](/api-reference/cases/count-cases) |
| `POST` | `/v1/cases` | [Create a new case](/api-reference/cases/create-case) |
| `GET` | `/v1/cases/{id}` | [Get case details with steps](/api-reference/cases/get-case) |
| `POST` | `/v1/cases/{id}/data` | [Update case data fields](/api-reference/cases/update-case-data) |
| `POST` | `/v1/cases/{id}/contacts` | [Update case contacts](/api-reference/cases/update-case-contacts) |
| `POST` | `/v1/cases/{id}` | [Change the status, archive or restore a case](/api-reference/cases/archive-case) |
| `POST` | `/v1/cases/{id}/start/{stepKey}` | [Start a case step](/api-reference/cases/start-case-step) |
## Tasks
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/cases/{id}/tasks` | [List the follow-up tasks of a case](/api-reference/tasks/list-case-tasks) |
| `POST` | `/v1/cases/{id}/tasks` | [Add a follow-up task to a case](/api-reference/tasks/create-case-task) |
| `PATCH` | `/v1/cases/{id}/tasks/{taskId}` | [Close, reopen or reassign a task](/api-reference/tasks/update-case-task) |
## Case Templates
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/case_templates` | [List case templates with filtering](/api-reference/case-templates/list-case-templates) |
| `GET` | `/v1/case_templates/{id}` | [Get case template details with steps](/api-reference/case-templates/get-case-template) |
## Attachments
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/attachments/{id}` | [Download single attachment](/api-reference/attachments/get-attachment) |
| `GET` | `/v1/attachments` | [Download multiple attachments as PDF](/api-reference/attachments/get-attachments-multiple) |
| `POST` | `/v1/attachments` | [Upload files (JSON or multipart)](/api-reference/attachments/upload-attachments) |
## Document Intelligence
| Method | Endpoint | Description |
| - | - | - |
| `POST` | `/v1/document_intelligence` | [Create document extraction job](/api-reference/document-intelligence/create-document-intelligence) |
| `GET` | `/v1/document_intelligence/{id}` | [Get extraction results](/api-reference/document-intelligence/get-document-intelligence) |
## Signatures
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/signatures/{id}` | [Get signature metadata or download file](/api-reference/signatures/get-signature) |
## Workspaces
| Method | Endpoint | Description |
| - | - | - |
| `GET` | `/v1/workspaces` | [List accessible workspaces](/api-reference/workspaces/list-workspaces) |
| `POST` | `/v1/workspaces` | [Provision a workspace for a client (partners only)](/api-reference/workspaces/provision-workspace) |
## Members
| Method | Endpoint | Description |
| - | - | - |
| `POST` | `/v1/members` | [Register a team member on a workspace](/api-reference/members/create-member) |
## Response Formats
Most endpoints return JSON responses by default. Some endpoints support multiple response formats:
### JSON Responses
* Default for most endpoints
* Set `Accept: application/json` header
### Binary Responses
* Attachments and signatures support `Accept: application/octet-stream`
* Returns file content for download
### PDF Responses
* Responses support `Accept: application/pdf`
* Returns rendered PDF of form response
## Authentication Requirements
All endpoints require authentication via Bearer token:
```bash theme={null}
Authorization: Bearer YOUR_ACCESS_TOKEN
```
## Common Query Parameters
Most list endpoints support these query parameters:
* **`page[number]`** - Page number (starts at 1)
* **`page[size]`** - Items per page (default: 10, max: 100)
* **`filter`** - JSON string with filter criteria
* **`sort`** - Sort field (prefix with `-` for descending)
* **`group-by`** - Group counts by field (count endpoints only)
See [Pagination & Sorting](/api-reference/pagination-and-sorting) and [Advanced Filters](/api-reference/advanced-filters) for detailed documentation.
## HTTP Methods
| Method | Usage |
| - | - |
| `GET` | Retrieve data (list, read, count) |
| `POST` | Create new resources or perform actions |
| `PATCH` | Update existing resources |
## Base URL
All endpoints are relative to:
```text theme={null}
https://connect.penbox.io/v1
```
## Related Resources
Detailed guide to API capabilities
OAuth2 authentication guide
Filter with operators and JSON queries
Control result size and order
# Get File
Source: https://docs.penbox.io/api-reference/attachments/get-attachment
GET /attachments/{id}
Download or retrieve metadata for a specific attachment.
Download or retrieve metadata for a specific file (API: `attachment`).
In the API, uploaded files are referred to as **attachments**. This is the technical term used in all endpoints and parameters.
```javascript Download File (JS) theme={null}
const fileId = '670e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://connect.penbox.io/v1/attachments/${fileId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/octet-stream'
}
}
);
const blob = await response.blob();
// Save or process the file
```
```javascript Get Metadata (JS) theme={null}
const fileId = '770e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://connect.penbox.io/v1/attachments/${fileId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/json'
}
}
);
const metadata = await response.json();
console.log('Filename:', metadata.name);
console.log('Size:', metadata.metadata.size);
console.log('Type:', metadata.type);
```
```bash Download File (cURL) theme={null}
curl -X GET 'https://connect.penbox.io/v1/attachments/670e8400-e29b-41d4-a716-446655440000' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Accept: application/octet-stream' \
--output downloaded-file.pdf
```
```bash Get Metadata (cURL) theme={null}
curl -X GET 'https://connect.penbox.io/v1/attachments/770e8400-e29b-41d4-a716-446655440000' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Accept: application/json'
```
## Response Formats
The endpoint supports two response formats controlled by the `Accept` header:
| Format | Accept Header | Use Case |
| - | - | - |
| **Binary** | `application/octet-stream` | Download the actual file |
| **JSON** | `application/json` | Get metadata and Base64 data (for files \< 50MB) |
### JSON Response
When using `Accept: application/json`, you'll receive:
```json theme={null}
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"name": "passport.pdf",
"type": "application/pdf",
"metadata": {
"size": 245678,
"width": null,
"height": null
},
"uri": "https://connect.penbox.io/v1/attachments/770e8400-e29b-41d4-a716-446655440000",
"data": "JVBERi0xLjQKJeLjz9MKJSVJU0lTIEREREVfUGRmLVY3LjEg..."
}
```
| Field | Type | Description |
| - | - | - |
| `id` | string | File UUID |
| `name` | string | Original filename |
| `type` | string | MIME type (e.g., `application/pdf`, `image/jpeg`) |
| `metadata.size` | number | File size in bytes |
| `metadata.width` | number | Image width in pixels (null for non-images) |
| `metadata.height` | number | Image height in pixels (null for non-images) |
| `uri` | string | Direct download URL |
| `data` | string | Base64-encoded file data (only for files \< 50MB) |
Files larger than 50MB cannot be retrieved as JSON. Use `Accept: application/octet-stream` to download large files.
## Bulk Download
Download multiple files as a single ZIP archive:
```bash theme={null}
curl -X GET 'https://connect.penbox.io/v1/attachments?ids=id1,id2,id3&filename=documents' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Accept: application/octet-stream' \
--output documents.zip
```
### Query Parameters
| Parameter | Type | Required | Description |
| - | - | - | - |
| `ids` | string | Yes | Comma-separated list of file UUIDs |
| `filename` | string | No | Custom filename for the downloaded archive |
Multiple files are packaged as a ZIP archive for download.
## Finding File IDs
File IDs are included when you retrieve a form response:
```javascript theme={null}
// Get response with files
const { data, included } = await fetch(
`https://connect.penbox.io/v1/responses/${responseId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
).then(r => r.json());
// Extract file IDs from included resources
const files = included.filter(item => item.type === 'attachments');
for (const file of files) {
console.log('File ID:', file.id);
console.log('Filename:', file.attributes.name);
console.log('Size:', file.attributes.metadata.size);
// Download the file
await downloadFile(file.id, accessToken);
}
```
## Supported File Types
Penbox supports various file types:
| Category | MIME Types |
| - | - |
| **Documents** | `application/pdf`, `application/msword`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
| **Images** | `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/svg+xml` |
| **Spreadsheets** | `application/vnd.ms-excel`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `text/csv` |
| **Archives** | `application/zip`, `application/x-rar-compressed`, `application/x-7z-compressed` |
| **Other** | `text/plain`, `application/json`, `application/xml` |
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - File downloaded or metadata retrieved |
| `400` | Bad Request - Missing `ids` parameter (for bulk download) |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No access to this file |
| `404` | Not Found - File doesn't exist |
| `413` | Payload Too Large - File too big for JSON response (use octet-stream) |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Files are permanently stored and remain accessible even after the form is archived.
For large files (> 50MB), always use `Accept: application/octet-stream`. The JSON endpoint will return a 413 error for files exceeding this limit.
Downloaded files should be scanned for viruses before processing, especially in production environments.
# Get Multiple Attachments
Source: https://docs.penbox.io/api-reference/attachments/get-attachments-multiple
GET /attachments
Download multiple attachments as a single PDF file.
Download multiple attachments as a single PDF file.
## Query Parameters
Comma-separated list of attachment UUIDs
Custom filename for the downloaded PDF (without extension)
## Response
Returns a PDF file containing all requested attachments merged together.
**Content-Type:** `application/pdf`
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - PDF generated |
| `400` | Bad Request - Missing or invalid ids parameter |
| `401` | Unauthorized - Invalid access token |
| `404` | Not Found - One or more attachments not found |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
This endpoint automatically merges multiple attachments into a single PDF document, which is useful for batch downloads or generating combined reports.
# Upload Attachment
Source: https://docs.penbox.io/api-reference/attachments/upload-attachments
POST /attachments
Upload one or more attachments. Supports both JSON (base64) and multipart/form-data formats.
Upload a single attachment to Penbox. Supports both JSON (base64) and multipart/form-data formats.
**Size limits.** Multipart uploads accept up to 10GB per file. JSON uploads accept a body of
20MB. That limit applies to the transmitted, base64-inflated payload, so roughly 15MB of file.
Downloads are limited to 50MB.
To store an uploaded document on a case field, follow
[Add a Document to a Case](/guides/upload-document-to-case) for the full sequence.
## Query Parameters
Workspace UUID. Optional for a token scoped to a single workspace. A token scoped to several
workspaces should name the target one — the first workspace of the token is used otherwise.
Note the bracket syntax: `?workspace[id]=`.
## Request Body (JSON)
For JSON uploads, send base64-encoded file data:
File name
Base64-encoded file content
MIME type (e.g., "application/pdf", "image/jpeg"). Optional - will be detected if not provided.
Not applied. Every upload is created with the same access rules: the workspace API token and
workspace admins can download the attachment. Use a step attachment if a contact or another member
needs access to the file.
## Request Body (Multipart)
For multipart uploads, use `multipart/form-data` with a file field.
## Response Structure
Returns a single attachment object:
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "document.pdf",
"type": "application/pdf",
"uri": "https://connect.penbox.io/v1/attachments/550e8400-e29b-41d4-a716-446655440000",
"metadata": {
"size": 1024
}
}
```
## Response Codes
| Code | Description |
| - | - |
| `201` | Created - File uploaded successfully |
| `400` | Bad Request - Invalid file data |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No company scope in token |
| `413` | Payload Too Large - JSON body over 20MB, or multipart file over 10GB |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Use multipart/form-data for large files. It is the only form that goes past 20MB, and it is
faster because nothing is base64-encoded. Keep the JSON form for small files.
# Authentication
Source: https://docs.penbox.io/api-reference/authentication
Authenticate API requests using Bearer tokens
The API uses **Bearer token authentication** for all requests. This simple and secure method requires you to include your API token in the `Authorization` header of every request.
## Getting Your API Token
### Step 1: Access API Settings
Navigate to your Penbox API settings page:
```text theme={null}
https://app.penbox.io/workspace/settings/api
```
### Step 2: Create a New Token
1. Click the **"Create API Token"** button
2. Give your token a descriptive name (e.g., "Production Integration", "Development", "Analytics Tool")
3. Select the workspace you want to authorize
4. Copy the generated token immediately
**Important:** Your API token will only be displayed once. Copy it immediately and store it securely. If you lose it, you'll need to generate a new token.
### Token Format
Your API token will look like this:
```text theme={null}
pnbx_1234567890abcdef1234567890abcdef1234567890abcdef
```
Tokens always start with the `pnbx_` prefix.
Building an integration that several Penbox workspaces authorize themselves? An [OAuth 2.0 flow](/api-reference/authentication-oauth2) with a client ID and client secret is available as an Enterprise feature. It is not the recommended method: a personal API token is simpler for most integrations.
## Using Your API Token
Include your API token in the `Authorization` header of every API request using the Bearer scheme:
```bash theme={null}
Authorization: Bearer pnbx_1234567890abcdef1234567890abcdef1234567890abcdef
```
### Example Requests
```bash cURL theme={null}
curl -X GET 'https://connect.penbox.io/v1/workspaces' \
-H 'Authorization: Bearer pnbx_1234567890abcdef1234567890abcdef1234567890abcdef'
```
```javascript JavaScript theme={null}
const response = await fetch('https://connect.penbox.io/v1/cases', {
headers: {
'Authorization': `Bearer ${apiToken}`
}
});
const forms = await response.json();
```
```python Python theme={null}
import requests
headers = {
'Authorization': f'Bearer {api_token}'
}
response = requests.get(
'https://connect.penbox.io/v1/forms',
headers=headers
)
forms = response.json()
```
```php PHP theme={null}
### Making Authenticated Requests
Every API endpoint requires authentication. Here's an example creating a form request:
```javascript theme={null}
const apiToken = 'pnbx_1234567890abcdef1234567890abcdef1234567890abcdef';
const response = await fetch('https://connect.penbox.io/v1/forms', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
flow: { slug: 'client-onboarding' },
user: {
email: 'client@example.com',
given_name: 'John',
family_name: 'Doe'
}
})
});
const form = await response.json();
console.log('Form created:', form.id);
```
## Token Scopes and Permissions
Each API token is associated with a specific workspace. The token will have access to:
* All forms in the workspace
* All cases in the workspace
* All form templates enabled for the workspace
* All files and attachments in the workspace
* Document intelligence features for the workspace
Tokens are workspace-specific. If you need to access multiple workspaces, create a separate token for each one.
## Token Security
### Best Practices
* Never commit tokens to version control (Git, SVN, etc.)
* Store tokens in environment variables or secure credential management systems
* Use secrets management services (AWS Secrets Manager, HashiCorp Vault, etc.)
* Never expose tokens in client-side code or browser JavaScript
Store your API token in environment variables:
```bash .env theme={null}
PENBOX_API_TOKEN=pnbx_1234567890abcdef1234567890abcdef1234567890abcdef
```
Then load it in your application:
```javascript theme={null}
const apiToken = process.env.PENBOX_API_TOKEN;
```
* Rotate tokens periodically for enhanced security
* Create a new token before revoking the old one to avoid downtime
* Update all applications using the old token
* Revoke the old token once migration is complete
Give your tokens meaningful names to track usage:
* ✅ "Production API - CRM Integration"
* ✅ "Development Environment"
* ✅ "Analytics Dashboard"
* ❌ "Token 1", "Test", "My Token"
* Track which applications use which tokens
* Monitor for unexpected API usage patterns
* Set up alerts for unusual activity
* Revoke tokens immediately if compromised
### What NOT to Do
**Never:**
* Commit tokens to Git repositories
* Share tokens via email, chat, or screenshots
* Store tokens in plain text files
* Use the same token across multiple environments
* Hardcode tokens in your source code
* Post tokens in public forums or documentation
## Managing Tokens
### Viewing Active Tokens
Visit [app.penbox.io/workspace/settings/api](https://app.penbox.io/workspace/settings/api) to see all your active API tokens:
* Token name
* Creation date
* Last used date
* Associated workspace
### Revoking Tokens
To revoke a token:
1. Go to [app.penbox.io/workspace/settings/api](https://app.penbox.io/workspace/settings/api)
2. Find the token you want to revoke
3. Click the **"Revoke"** button
4. Confirm the action
Once revoked, a token is immediately invalidated. All API requests using that token will fail with a `401 Unauthorized` error.
### Token Lifecycle
```mermaid theme={null}
graph LR
A[Create Token] --> B[Use Token]
B --> C{Still Needed?}
C -->|Yes| B
C -->|No| D[Revoke Token]
C -->|Rotate| E[Create New Token]
E --> F[Update Applications]
F --> D
```
## Authentication Errors
### Common Error Responses
#### 401 Unauthorized - Missing Token
```json theme={null}
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing authorization header"
}
```
**Cause:** The `Authorization` header is missing from your request.
**Solution:** Add the `Authorization` header with your Bearer token.
#### 401 Unauthorized - Invalid Token
```json theme={null}
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid token"
}
```
**Cause:** The token is invalid, expired, or has been revoked.
**Solution:** Check your token is correct, or create a new token if it was revoked.
#### 403 Forbidden - No Access
```json theme={null}
{
"statusCode": 403,
"error": "Forbidden",
"message": "No access to this resource"
}
```
**Cause:** Your token doesn't have access to the requested resource (e.g., wrong workspace).
**Solution:** Verify you're using the correct token for the workspace you're trying to access.
### Debugging Authentication Issues
If you're experiencing authentication issues:
1. **Verify the token format**
* Token should start with `pnbx_`
* No extra spaces or newlines
* Complete token copied
2. **Check the Authorization header**
```bash theme={null}
Authorization: Bearer pnbx_your_token_here
```
* Includes "Bearer " prefix with a space
* Token immediately after the space
3. **Verify token is active**
* Check [app.penbox.io/workspace/settings/api](https://app.penbox.io/workspace/settings/api)
* Ensure token hasn't been revoked
* Confirm token exists in the list
4. **Test with cURL**
```bash theme={null}
curl -v -X GET 'https://connect.penbox.io/v1/workspaces' \
-H 'Authorization: Bearer YOUR_TOKEN'
```
The `-v` flag shows detailed request/response information.
## Rate Limiting
API tokens are subject to rate limiting to ensure fair usage and system stability.
### Rate Limit Exceeded
If you exceed the rate limit, you'll receive a `429 Too Many Requests` response:
```json theme={null}
{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please try again later.",
"retryAfter": 60
}
```
### Best Practices for Rate Limiting
* Cache responses when possible
* Implement exponential backoff on errors
* Monitor your usage patterns
* Spread requests over time instead of bursting
* Use webhooks instead of polling when possible
## Testing Your Authentication
### Quick Test
Test your token with a simple request to list workspaces:
```bash theme={null}
curl -X GET 'https://connect.penbox.io/v1/workspaces' \
-H 'Authorization: Bearer YOUR_TOKEN'
```
**Expected response (success):**
```json theme={null}
{
"data": [
{
"id": "...",
"slug": "my-workspace",
"name": "My Workspace"
}
]
}
```
**Error response (invalid token):**
```json theme={null}
{
"statusCode": 401,
"error": "Unauthorized"
}
```
### Integration Checklist
Before deploying to production:
* [ ] Token stored securely (environment variable or secrets manager)
* [ ] Authorization header correctly formatted
* [ ] Error handling implemented for 401/403 responses
* [ ] Rate limiting handled with backoff strategy
* [ ] Token name clearly identifies the integration
* [ ] Monitoring/logging in place for API usage
* [ ] Token rotation plan documented
## Multiple Environments
For different environments, create separate tokens:
### Development
```bash theme={null}
PENBOX_API_TOKEN_DEV=pnbx_dev_token_here
```
### Staging
```bash theme={null}
PENBOX_API_TOKEN_STAGING=pnbx_staging_token_here
```
### Production
```bash theme={null}
PENBOX_API_TOKEN_PROD=pnbx_production_token_here
```
Then load the appropriate token based on your environment:
```javascript theme={null}
const apiToken = process.env.NODE_ENV === 'production'
? process.env.PENBOX_API_TOKEN_PROD
: process.env.PENBOX_API_TOKEN_DEV;
```
## Next Steps
Learn how to create forms
Retrieve your authorized workspaces
Complete API capabilities guide
Browse all available endpoints
## Support
If you encounter issues with authentication:
1. Check this documentation for troubleshooting steps
2. Verify your token at [app.penbox.io/workspace/settings/api](https://app.penbox.io/workspace/settings/api)
3. Test with the cURL examples above
4. Contact support at [support@penbox.io](mailto:support@penbox.io) with:
* Token name (never send the actual token)
* Error messages received
* API endpoint being called
* Request timestamp
# OAuth 2.0
Source: https://docs.penbox.io/api-reference/authentication-oauth2
Authenticate on behalf of Penbox workspaces with OAuth 2.0, using a client ID and client secret
**This is not the recommended way to authenticate.** For most integrations, use a [personal API token](/api-reference/authentication): it is simpler and needs no setup on our side. Choose OAuth 2.0 only if you need each workspace to authorize your application itself.
**Enterprise feature.** OAuth 2.0 is available on the Enterprise plan. Contact your Penbox account manager to enable it.
Use OAuth 2.0 when you build an **integration used by several Penbox workspaces** (a partner platform, a marketplace app). Each workspace admin authorizes your application once, and you receive a Bearer token for that workspace, without ever handling a personal API token.
OAuth 2.0 clients are provisioned by Penbox. There is no self-service screen to create one yet. Contact [support@penbox.io](mailto:support@penbox.io) with your application name, logo, website and the HTTPS redirect URI(s) you will use. You receive a `client_id` and a `client_secret` in return.
## How it works
Penbox supports the **Authorization Code** flow, with refresh tokens.
```mermaid theme={null}
sequenceDiagram
participant U as Workspace admin
participant A as Your application
participant P as Penbox (connect.penbox.io)
A->>U: Redirect to /authorize
U->>P: Sign in and pick a workspace
P->>A: Redirect to your redirect_uri with ?code=...
A->>P: POST /token (code + client_id + client_secret)
P->>A: access_token + refresh_token
A->>P: API calls with Authorization: Bearer access_token
```
The `client_secret` must stay on your server. Never ship it in a browser app or a mobile app.
## Endpoints
| Environment | Base URL |
| - | - |
| Production | `https://connect.penbox.io` |
| Staging | `https://connect.aiboov.com` |
The authorization and token endpoints live at the root of the host, not under `/v1`. The server metadata is published at `/.well-known/openid-configuration`.
## Step 1: Send the user to Penbox
Redirect the user's browser to `/authorize`:
```text theme={null}
https://connect.penbox.io/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https%3A%2F%2Fapp.example.com%2Fpenbox%2Fcallback
&response_type=code
&state=RANDOM_VALUE
```
| Parameter | Required | Description |
| - | - | - |
| `client_id` | Yes | The client ID Penbox gave you. |
| `redirect_uri` | Yes | Must use `https://` and match one of the URIs registered for your client. It cannot contain `code`, `state`, `error` or `error_description` query parameters. |
| `response_type` | Yes | Use `code`. |
| `state` | Recommended | An opaque value returned unchanged to your redirect URI. Use it to prevent CSRF. |
| `scope` | No | Defaults to `offline_access`, which returns a refresh token. |
The user signs in, selects the workspace to authorize, and approves your application.
## Step 2: Receive the authorization code
Penbox redirects the user back to your `redirect_uri`:
```text theme={null}
https://app.example.com/penbox/callback?code=AUTHORIZATION_CODE&state=RANDOM_VALUE
```
Check that `state` matches what you sent. If something went wrong, the redirect carries `error` and `error_description` instead of `code`.
## Step 3: Exchange the code for tokens
Call `POST /token` from your server with your credentials:
```bash cURL theme={null}
curl -X POST 'https://connect.penbox.io/token' \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "authorization_code",
"code": "AUTHORIZATION_CODE",
"redirect_uri": "https://app.example.com/penbox/callback",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
```
```javascript JavaScript theme={null}
const response = await fetch('https://connect.penbox.io/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code,
redirect_uri: 'https://app.example.com/penbox/callback',
client_id: process.env.PENBOX_CLIENT_ID,
client_secret: process.env.PENBOX_CLIENT_SECRET
})
});
const tokens = await response.json();
```
The body can be JSON or `application/x-www-form-urlencoded`. Instead of the body, you can also send the credentials with HTTP Basic authentication (`Authorization: Basic base64(client_id:client_secret)`).
If you sent a `redirect_uri` in step 1, send the same value here.
**Response:**
```json theme={null}
{
"access_token": "eyJhbGciOi...",
"id_token": "eyJhbGciOi...",
"refresh_token": "d41f...",
"token_type": "Bearer",
"expiresIn": 86400
}
```
| Field | Description |
| - | - |
| `access_token` | The Bearer token for API calls. Valid for 24 hours (`expiresIn`, in seconds). It is scoped to the workspace the user selected. |
| `refresh_token` | Returned when the `offline_access` scope was granted (the default). Use it to get a new access token. |
| `id_token` | Basic profile of the user who authorized (name, email, locale). |
## Step 4: Call the API
Use the access token exactly like a personal API token:
```bash theme={null}
curl -X GET 'https://connect.penbox.io/v1/cases' \
-H 'Authorization: Bearer ACCESS_TOKEN'
```
## Refreshing the access token
Before the access token expires, request a new one:
```bash theme={null}
curl -X POST 'https://connect.penbox.io/token' \
-H 'Content-Type: application/json' \
-d '{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}'
```
The response contains a new `access_token`. The refresh token stays the same, so keep storing it.
If the refresh token is rejected (`400`), send the workspace admin through the authorization flow again.
## Errors
| Status | Cause |
| - | - |
| `401` | Missing or invalid `client_id` / `client_secret`, unknown or invalid `code`, `redirect_uri` that differs from the one used in step 1, or unsupported `grant_type`. |
| `400` | Malformed request, or invalidated refresh token. |
At the `/authorize` step, errors are sent back to your `redirect_uri` as `error` and `error_description`. If the `redirect_uri` itself is invalid, the error is shown to the user instead of redirecting.
## Security checklist
* Store `client_secret` and refresh tokens in a secrets manager, never in source control or client-side code.
* Always send and verify `state`.
* Register exact HTTPS redirect URIs.
* Keep one access token and one refresh token per authorized workspace.
* Ask Penbox to rotate your `client_secret` if it may have leaked.
## Next steps
Use a personal API token for your own workspace
HTTP status codes returned by the API
# Get Case Template
Source: https://docs.penbox.io/api-reference/case-templates/get-case-template
GET /case_templates/{id}
Retrieve complete details of a specific case template including its steps and schema.
Retrieve complete details of a specific case template including its steps and schema.
This endpoint returns the full template structure including all configured steps and data schema fields. Use this to understand what a template workflow looks like before creating cases from it.
## Response Details
The response includes:
* **Basic template information**: title, description, reference, etc.
* **Steps**: The complete workflow steps configuration
* **Schema**: Data fields and their types that cases created from this template will have
* **Contacts**: Contact roles configured in the template
* **Statuses**: The custom statuses configured in the template, each with its `key`, human-readable `label` and `parent_status`
Cases created from this template inherit these statuses and return them in their own `statuses` array, so resolving a case status key into a label does not require fetching the template.
# List Case Templates
Source: https://docs.penbox.io/api-reference/case-templates/list-case-templates
GET /case_templates
Retrieve a paginated list of case templates (template workflows) with optional filtering and sorting. Case templates are reusable workflow blueprints.
Retrieve a paginated list of case templates with optional filtering and sorting.
Case templates are reusable workflow blueprints that can be instantiated into actual cases.
Use the `title` filter to search for templates by name with case-insensitive partial matching. This makes it easy to find the right template quickly.
## Common Use Cases
* List all available case templates in a workspace
* Search for templates by title
* Filter templates by owner or workspace
* Find archived vs active templates
* Review the statuses configured on each template through the `statuses` array
# Apply Case Input
Source: https://docs.penbox.io/api-reference/cases/apply-case-input
POST /cases/{id}/inputs/{input_id}/apply
Review a `suggest` input: accept some rows, optionally with an edited value, and reject the rest. Deterministic, no AI call, applied from the stored record.
- An accepted row is written whatever the fill setting says: a human validated it.
- A row whose field changed on the case since the proposal comes back `skipped` with `reason: value_changed`, so an old proposal never overwrites a newer value.
- Idempotent: rows already applied or rejected are returned unchanged. An unknown row id is a 400.
- `reject_others` (default `true`) rejects every row you did not list. Pass `false` to keep them proposed for a later call.
- A `document` row is filed at apply time. Pass `key` to file an `unclassified` document, or to send a classified one to another field. Document Intelligence runs after this response, so re-read the case a moment later when a row comes back with `"intelligence": "pending"`.
Review an input created in `suggest` mode: accept some rows, optionally with an edited value, and reject the rest.
A `document` row is filed at apply time. Pass `key` on the row to choose the file field. Use it
to file a document that came back `unclassified`, or to send a classified one to another field.
The key must be a file field of the case, else the call is a 400.
```json Filing a document on apply theme={null}
{
"changes": [
{ "id": "c1" },
{ "id": "c7", "key": "photos" }
]
}
```
Document Intelligence runs after this response, and only when the target field has a Document
Intelligence template on the case. A row that comes back with `"intelligence": "pending"` means
values read from the file will land on the case shortly: re-read `GET /cases/{id}` a moment
later. Document Intelligence is billed per page.
# Update Case
Source: https://docs.penbox.io/api-reference/cases/archive-case
POST /cases/{id}
Move a case to another status, archive it, or restore it
Update a case. Send `status` to move the case through its lifecycle, `archived` to
archive or restore it, or both in the same call. At least one of the two is required.
## Statuses
These five keys are always accepted:
| Key | Meaning |
| - | - |
| `draft` | not started yet |
| `pending` | waiting on someone |
| `in_progress` | being worked on |
| `closed` | finished |
| `cancelled` | dropped |
A case created from a case template also accepts that template's **custom** status
keys. Read the accepted keys from the `statuses` array of
[Get Case](/api-reference/cases/get-case) — you never have to hardcode them.
There is no transition matrix: any accepted key can follow any other, reopening a
closed case included. This is the same freedom a user has inside Penbox.
`parent_status` is derived by Penbox from the status you set and cannot be written.
A status change made with an API token is written to the case timeline and
attributed to **your integration by name**, so a user reading the case in Penbox
can tell your change apart from a Penbox automation.
Changing a status does not run the case automations. Automations move the status,
not the other way round — exactly as when a user changes the status by hand.
Case templates cannot be updated through this endpoint. Calling it on a template
returns a `400`.
```bash Move the case forward theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "status": "in_progress" }'
```
```bash Close the case theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "status": "closed" }'
```
```bash Archive theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "archived": true }'
```
```bash Unarchive theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "archived": false }'
```
```json 200 theme={null}
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"title": "Onboarding - Acme Corp",
"status": "in_progress",
"status_label": "In Progress",
"parent_status": "in_progress",
"archived_at": null
}
```
```json 422 theme={null}
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Invalid status \"done\". Valid statuses for this case: draft, pending, in_progress, closed, cancelled"
}
```
```json 400 theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": "Cannot update a template"
}
```
# Count Cases
Source: https://docs.penbox.io/api-reference/cases/count-cases
GET /cases/count
Get the total count of cases matching your filter criteria.
Get the total count of cases matching your filter criteria.
This endpoint accepts the same filters as [List Cases](/api-reference/cases/list-cases), including the
free-text `contact[search]`, so a count always matches the list it belongs to:
```
GET /v1/cases/count?contact[search]=M%C3%A9lanie%20Hardy
```
# Create Case
Source: https://docs.penbox.io/api-reference/cases/create-case
POST /cases
Create a new case (workflow) with contacts and data schema.
Create a new case (workflow) with contacts and data schema.
Contacts are automatically created or linked if they already exist in the system based on their email address.
For an API token, `workspace.id` is **required** — it is never inferred from the token.
A case created through this endpoint is stamped `channel: "api"` and `actor_type: "system"`, with a null `creator`. These three fields are read-only: a value sent in the body is ignored.
```bash From a template theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"title": "Dupont vs. Acme",
"workspace": { "id": "" },
"template": { "id": "" },
"owner": { "email": "amina@firm.example" },
"contacts": [{ "key": "main", "email": "j.dupont@mail.example" }]
}'
```
# Create Case Input
Source: https://docs.penbox.io/api-reference/cases/create-case-input
POST /cases/{id}/inputs
Push raw material on a case (a call transcript, CRM notes, a chat thread, a parsed email as JSON) and let Penbox turn it into case data, the way an inbound email is read.
This is the front door for writing to a case. `data` covers `POST /cases/{id}/data`, `contacts` covers `POST /cases/{id}/contacts`, and `content` is what neither can take. Use the atomic routes when you already know exactly which key to write; use this route when you hold material Penbox should read, or when you want to know what was written **and what was refused**.
At least one of `content`, `data`, `contacts`, `documents` is required. A call carrying only `data` / `contacts` / `documents` with an explicit `key` runs no AI.
- `auto` mode (the default, configurable per case template) writes the values right away, within the case's Email Intelligence *fill* setting: by default an input fills empty fields and never overwrites a value the case already holds.
- `suggest` mode keeps every row as a proposal. Review it with `POST /cases/{id}/inputs/{input_id}/apply`.
Explicit values always win over what the AI reads. Values land on the case in a single write, so automations and Document Intelligence fire as they do for `POST /cases/{id}/data`; Document Intelligence runs after the response, so re-read the case a moment later if a file field with a template was written.
**Documents.** Send up to 5 files in `documents`. A file is either an attachment you already uploaded with `POST /attachments` (`{ "id": "…" }`), or an inline base64 file (`{ "name", "type", "data" }`). The whole request body must stay under 50 MB. Upload a big file with `POST /attachments` first and send its id: that route takes files up to 10 GB with multipart/form-data. Penbox classifies each file and files it on the right file field of the case. Classification is synchronous: the response tells you where every document went. Add `"key": ""` to a document to file it on that field yourself, without classification. `options.classify_documents: false` switches the classifier off: a document without a `key` comes back `unclassified` and stays unfiled on the workspace.
**Document Intelligence.** Filing a document can trigger Document Intelligence. It runs only when the target file field has a Document Intelligence template configured on the case, and it runs after the response. A document row with `"intelligence": "pending"` means values read from the file will land on the case shortly: re-read `GET /cases/{id}` a moment later. In `suggest` mode the file is uploaded but not filed, so Document Intelligence runs at apply time. Document Intelligence is billed per page. Classification is not billed separately.
Push raw material on a case and let Penbox read it into case data, the way an inbound email is read.
Use this route when you hold something Penbox should read: a call transcript, CRM notes, a chat thread, a parsed email. Use `POST /cases/{id}/data` when you already know exactly which key to write.
The response is the input resource: what Penbox proposed or wrote, row by row, and what it refused and why. The refreshed case is embedded under `case`.
Contacts sent in `contacts` are the caller's own word: the role is created when the
case does not declare it, an already-bound role is replaced, and the identity fields
you send override the ones on an existing contact. A contact the AI read is more
conservative: it fills a declared role, and only when that role is empty.
## New fields
The AI may add new fields to the case for information that matches no existing field. This is
controlled by `options.create_fields`. It defaults to the case setting, then `true`. Send
`false` to keep that information in `unmapped` instead.
This is how you fill a case created without a template. Post one input per message and the case
builds up as the conversation goes.
## Documents
Send files in `documents`, 5 at most per call. Each file is either an attachment you already
uploaded, or an inline base64 file.
```json Two ways to send a file theme={null}
"documents": [
{ "id": "6f1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d" },
{ "name": "quote.pdf", "type": "application/pdf", "data": "JVBERi0xLjQK…" }
]
```
The whole request body must stay under 50 MB. For a big file, upload it first with
`POST /attachments`, then send its id here. That route takes files up to 10 GB.
Penbox reads each file and files it on the right file field of the case. This classification
happens during the request. The response tells you where every document went.
Add a `key` to a document to file it on that field yourself. The classifier is then skipped
for that file. The key must be a file field of the case, else the call is a 400 with the code
`not_file_field`.
```json Filed on an explicit field theme={null}
{ "name": "photo.jpg", "type": "image/jpeg", "data": "/9j/4AAQ…", "key": "photos" }
```
Set `options.classify_documents` to `false` to switch the classifier off. Documents without a
`key` then come back with the status `unclassified`. The file is uploaded and kept on the
workspace, but it is not filed on the case. You can file it later with
`POST /cases/{id}/inputs/{input_id}/apply`.
`intelligence.documents` says how the classification went: `succeeded`, `partial` (at least one
file could not be classified), `failed` (none could) or `skipped` (no file needed the
classifier). A file that fails classification comes back `unclassified`. The upload is never
lost.
A file the case already holds is not classified twice. Penbox matches on the attachment id, or
on name + size + type. When both sides carry a checksum and the checksums differ, it is not a
duplicate. The match runs against every file field of the case, not only the one the classifier
would have picked: a document already filed elsewhere must not land under a second field.
The row comes back `skipped`, with `reason: duplicate` and the `key` of the field that already
holds the file. A document sent with an explicit `key` skips this check: it is filed as you
asked.
```json A document row theme={null}
{
"id": "c5",
"kind": "document",
"key": "quote",
"label": "Quote",
"attachment": {
"id": "6f1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d",
"name": "quote.pdf",
"size": 128413,
"type": "application/pdf"
},
"origin": "ai",
"status": "applied",
"intelligence": "pending"
}
```
## Document Intelligence
Filing a document can trigger Document Intelligence. It runs only when the target file field
has a Document Intelligence template configured on the case. It runs after the response, not
during the request.
A document row with `"intelligence": "pending"` means values read from the file will land on
the case shortly. Re-read `GET /cases/{id}` a moment later to see them. The change list of the
input never carries them.
In `suggest` mode the file is uploaded but not filed. Document Intelligence therefore runs
after the apply call, not after this one.
Document Intelligence is billed per page, exactly as it is on `POST /document_intelligence`.
Classification is not billed separately.
# Get Case
Source: https://docs.penbox.io/api-reference/cases/get-case
GET /cases/{id}
Retrieve complete details of a specific case including its steps and schema.
Retrieve complete details of a specific case including its steps and schema.
The `status` field contains the key of the status, not its label. Use `status_label` to display it directly, or the `statuses` array — returned with the case — to map every `key` to its `label`. Neither requires an extra call.
Custom statuses return the label configured on the case template. Built-in statuses (`draft`, `pending`, `in_progress`, `closed`, `cancelled`) return an English label: translate them from the key if you need another language.
**Where the case came from.** Three read-only fields say how a case was created. `actor_type` says who decided: `human` a person, `ai` a model, `automation` a rule configured on a case template, `partner` a partner integration, `system` Penbox itself or an API token. `channel` says which surface the case came through: `app`, `outlook`, `mcp`, `api`, `email`, `form`, `connection` or `system`. `creator` is the workspace member who created the case, or null when no person did.
You cannot write these fields. A value sent in a request body is ignored. They are null on cases created before this was recorded.
# Get Case Input
Source: https://docs.penbox.io/api-reference/cases/get-case-input
GET /cases/{id}/inputs/{input_id}
One input, with its raw `content` and the current case. This is the "see the input" read.
One input, with the raw material it was created from.
A `document` row keeps the file it filed under `attachment`. A row still marked `unclassified`
has an uploaded file waiting for a field: file it with
`POST /cases/{id}/inputs/{input_id}/apply` and a `key`.
# List Case Inputs
Source: https://docs.penbox.io/api-reference/cases/list-case-inputs
GET /cases/{id}/inputs
The inputs of a case, newest first. The list drops `changes`, `unmapped` and `content`, and carries `changes_count` / `applied_count` instead.
The inputs of a case, newest first.
The list drops the change rows, so it does not tell you where a document was filed. Read one
input to see that.
# List Cases
Source: https://docs.penbox.io/api-reference/cases/list-cases
GET /cases
Retrieve a paginated list of cases (workflows) with optional filtering and sorting.
Retrieve a paginated list of cases with optional filtering and sorting.
Cases are also referred to as **workflows** in the API. They represent structured processes with multiple steps and contacts.
Use the `parent_status` filter to quickly find cases in specific workflow stages, and combine with `waiting_for` to identify bottlenecks.
The `status` field contains the key of the status, not its label. Each case also carries `status_label`, the label of its current status, and a `statuses` array mapping every `key` to a human-readable `label` — so a list can be rendered without fetching the case templates. Built-in statuses (`draft`, `pending`, `in_progress`, `closed`, `cancelled`) are labelled in English.
**Looking for a case by who it is about?** Use `contact[search]` — one free-text query, matched
against the contact's name, email, phone, internal reference and company name at once:
```
GET /v1/cases?contact[search]=M%C3%A9lanie%20Hardy
```
Every word must match, in any order, anywhere in the contact, and accents are ignored in both
directions — so `Mélanie Hardy`, `hardy mel` and `melanie hardy` all find the same person, and
`Jorgensen` finds `Jørgensen`.
The `contact[given_name]`, `contact[family_name]` and `contact[email]` filters are **exact match**
instead: `contact[given_name]=Mélanie` will not find a contact stored as `MELANIE`. Reach for them
only when you have the precise stored value — otherwise use `contact[search]`.
**Where each case came from.** Every case in the list carries `actor_type` (who decided: `human`, `ai`, `automation`, `partner` or `system`), `channel` (the surface it came through: `app`, `outlook`, `mcp`, `api`, `email`, `form`, `connection` or `system`) and `creator` (the workspace member who created it, or null when no person did). The three fields are read-only, and null on cases created before this was recorded.
# Start Case Step
Source: https://docs.penbox.io/api-reference/cases/start-case-step
POST /cases/{id}/start/{stepKey}
Start (execute) a specific step in a case
This endpoint triggers the execution of a specific step within a case. When a step is started, it performs its configured actions such as:
* Sending email notifications to contacts or case owners
* Sending SMS notifications
* Creating and sending form requests
* Creating internal notes or tasks
* Triggering automations
## Notes
* You cannot start steps in template cases (is\_template: true)
* Starting a step may trigger automations configured for the case
* The step must be defined in the case's step configuration
* Some steps may have execution conditions that prevent them from running if not met
# Update Case Contacts
Source: https://docs.penbox.io/api-reference/cases/update-case-contacts
POST /cases/{id}/contacts
Replace the contacts for a specific case. This will replace all existing contacts with the provided contacts.
Add or modify contacts in a case.
# Update Case Data
Source: https://docs.penbox.io/api-reference/cases/update-case-data
POST /cases/{id}/data
Update multiple data fields in a case using a bulk operation.
Update multiple data fields in a case using a bulk operation.
To set a file field, upload the document first and save the reference it returns — see
[Add a Document to a Case](/guides/upload-document-to-case).
# Create Document Intelligence
Source: https://docs.penbox.io/api-reference/document-intelligence/create-document-intelligence
POST /document_intelligence
Extract structured data from documents using AI-powered document intelligence.
Extract structured data from documents using AI-powered document intelligence.
To use this feature, activation is required on your workspace. Please contact us for more
information.
## Request Body
Company reference (will use first workspace from token if ommited)
Company UUID
Array of attachments to process (minimum 1 attachment). Each attachment must use **either** the `id` field (reference existing) **OR** the `data` field (upload new).
Base64-encoded file content
File name (required when using `data`)
MIME type (required when using `data`, e.g., "application/pdf", "image/jpeg")
Existing attachment UUID
File name (optional when using `id`)
MIME type (optional when using `id`)
Exactly one of `fields` or `template` must be provided — not both.
Array of fields to extract from the documents. Mutually exclusive with `template`.
Field name to extract
Optional key for the field
Hint to help the AI extract the field (optional).
Can be used to specify the format of the field, for example: "DD/MM/YYYY" for a date field.
Field type: `text`, `number`, `date`, or `list`. Use `list` for repeating items; when `list`, provide a nested `fields` array defining the structure of each item.
Sub-field name to extract
Key for the sub-field (required for list items)
Hint to help the AI extract the sub-field
Sub-field type: `text`, `number`, or `date`
Reference to a document intelligence template. Mutually exclusive with `fields`.
Document intelligence template UUID
Optional language hint for document processing
Extraction quality level: `fast` or `advanced`. It sets both the model used and the credit cost per
page — `fast` costs 3 credits per page, `advanced` costs 6. Defaults to `advanced`.
Only valid together with `fields`. When you use a `template`, the level configured on that template
applies instead, and sending `level` alongside `template` is rejected.
The document intelligence processing may take a few seconds. The response includes the extracted
results in the `results` field once processing is complete.
Use the `hint` field to help the AI extract the field. For example, if the field is a date, you
can use the `hint` field to specify the format of the date. For example: "DD/MM/YYYY".
You can reference existing attachments by their UUID instead of uploading new ones, which is more
efficient if the documents are already in the system.
# Get Document Intelligence
Source: https://docs.penbox.io/api-reference/document-intelligence/get-document-intelligence
GET /document_intelligence/{id}
Retrieve the results of a document intelligence extraction job.
Retrieve the results of a document intelligence extraction job.
The `results` object contains the extracted field values with the same keys you specified when creating the job. Values are automatically typed based on the field type (text, number, or date).
# Error Codes
Source: https://docs.penbox.io/api-reference/error-codes
Complete reference of the HTTP status codes returned by the Penbox API and how to handle them
The Penbox API uses conventional HTTP status codes to indicate the success or
failure of a request. The same set of codes applies consistently across **all
endpoints** — creating a Case behaves the same way as any other call.
## Success codes
| Code | Name | Description |
| - | - | - |
| **200** | `OK` | The request succeeded. Returned for reads, updates, **and for creating a Case** (`POST /cases`). |
| **201** | `Created` | A resource was created successfully (e.g. attachment upload, document intelligence). |
| **202** | `Accepted` | The request was accepted and will be processed asynchronously (e.g. webhooks / events). |
## Client error codes (4xx)
| Code | Name | Description |
| - | - | - |
| **400** | `Bad Request` | The request is invalid: a required field is missing, the JSON is malformed, a UUID or email is invalid, a schema value is incorrect, or a referenced template/owner could not be found. |
| **401** | `Unauthorized` | Authentication failed: the `Authorization` header is missing, or the token/JWT is invalid or expired. |
| **403** | `Forbidden` | Authenticated but not allowed: the token does not have access to the requested workspace or resource. |
| **404** | `Not Found` | The resource does not exist (unknown id for a case, template, workspace, form…) or the endpoint does not exist. |
| **409** | `Conflict` | The request conflicts with the current state of the resource (e.g. a step already started, a duplicate). |
| **413** | `Payload Too Large` | The request body is too large (e.g. a file upload exceeding the size limit). |
| **422** | `Unprocessable Entity` | The request is well-formed but fails a business validation rule. |
## Server error codes (5xx)
| Code | Name | Description |
| - | - | - |
| **500** | `Internal Server Error` | An unexpected error occurred on our side. |
| **502** | `Bad Gateway` | A backend service is unreachable or returned an error. |
| **503** | `Service Unavailable` | The service is temporarily unavailable (infrastructure level). |
| **504** | `Gateway Timeout` | The request took too long to complete (timeout ≈ 30 s). |
**`429 Too Many Requests`** is not produced by the application itself, but may be returned by the
network / gateway layer if rate limiting is in effect.
## Error response body
All error responses share the same JSON structure:
```json theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": "A human-readable description of the error",
"errors": [
{
"message": "validation detail",
"path": ["field"],
"type": "..."
}
]
}
```
| Field | Description |
| - | - |
| `statusCode` | The HTTP status code, repeated in the body for convenience. |
| `error` | The official HTTP status name (e.g. `Bad Request`). |
| `message` | A human-readable description of what went wrong. |
| `errors` | Present only for validation errors (`400`). An array detailing each invalid field. |
When handling errors programmatically, branch on the HTTP `statusCode` rather than parsing the
`message`, which is meant for humans and may change.
# Get Form Template Options
Source: https://docs.penbox.io/api-reference/flows/get-flow-options
GET /form_templates/{slug}/options
Retrieve the configuration options for a specific form template.
Retrieve the configuration options for a specific form template.
The exact structure of the options object depends on the form template configuration. Different form templates may have different available options.
# Get Form Template Steps
Source: https://docs.penbox.io/api-reference/flows/get-flow-steps
GET /form_templates/{slug}/steps
Retrieve the form steps/structure for a specific form template.
Retrieve the form steps/structure for a specific form template.
Use this endpoint to understand the structure of a form template before creating forms, or to build a custom form UI.
# List Form Templates
Source: https://docs.penbox.io/api-reference/flows/list-flows
GET /form_templates
Retrieve a paginated list of form templates available in the authorized workspaces.
Retrieve all active form templates available in the authorized workspaces.
Only **enabled** and **published** templates are returned by this endpoint. Archived or disabled templates require the `?archived=true` query parameter.
Cache template information to improve performance. Template configurations don't change frequently, so you can refresh this data periodically (e.g., hourly or daily).
Template slugs can be reused across different workspaces. Always combine the template slug with the workspace slug when storing or referencing templates in your system.
# Introduction
Source: https://docs.penbox.io/api-reference/introduction
Get started with the Penbox API
Welcome to the Penbox API documentation. Build powerful integrations for form management, workflow automation, and document processing.
## Quick Start
Login to [app.penbox.io/workspace/settings/api](https://app.penbox.io/workspace/settings/api) and create a new API token.
[Learn about Authentication →](/api-reference/authentication)
Use your token to authenticate API requests:
```bash theme={null}
curl https://connect.penbox.io/v1/workspaces \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
Browse available endpoints and try them in the interactive documentation.
## API Essentials
**Base URL:** `https://connect.penbox.io/v1`
**Authentication:** All requests require a Bearer token in the `Authorization` header.
**Rate Limits:**
* 100 requests per minute per token
* 1,000 requests per hour per client
## Available Resources
List authorized workspaces
Create and manage workflow cases with steps
Reusable workflow blueprints for creating cases
Create and manage forms, responses, and templates
Upload and download attachments
Extract structured data from documents using AI
Download and manage digital signatures
## Developer Guides
Complete guide to API capabilities and best practices
Quick reference of all available endpoints
OAuth2 authentication and token management
Powerful filtering with operators and JSON queries
Control result size and order
## Need Help?
* **Documentation**: Browse the detailed guides above
* **Support**: [support@penbox.io](mailto:support@penbox.io)
* **Status**: status.penbox.io
# Create Member
Source: https://docs.penbox.io/api-reference/members/create-member
POST /members
Register a member of your client's team on a workspace
Register a member of your client's team on a workspace. Cases then resolve that person as an owner or an assignee, instead of mistaking the team for contacts.
Members created through the API are always **silent**: Penbox never emails them, and the flag cannot be overridden from the payload. A partner can never create a member Penbox would contact.
## Idempotency
The call is idempotent on the pair (`workspace.id`, `email`): re-posting a member who already exists returns that member with a `200` instead of failing on the uniqueness constraint. A `201` means the member was created.
Emails are lowercased and trimmed before the lookup, so `Jane@Acme.com` and `jane@acme.com` are the same member.
## Roles
| Field | Default | Meaning |
| - | - | - |
| `is_handler` | `true` | Can be set as the owner or an assignee of a case |
| `is_admin` | `false` | Can administer the workspace and its settings |
`workspace.id` is always explicit — it is never inferred from the token. A token that does not cover the workspace returns a `404`.
# Pagination and Sorting
Source: https://docs.penbox.io/api-reference/pagination-and-sorting
Control result size and order with pagination and sorting parameters
The Penbox API provides pagination and sorting capabilities on all list endpoints to help you manage large datasets and control the order of results.
## Pagination
All list endpoints support pagination to manage large result sets efficiently.
### Basic Usage
```javascript theme={null}
// Method 1: Direct parameters
const params = new URLSearchParams({
'page[number]': '1',
'page[size]': '50',
'workspace[slug]': 'acme-corp'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
// Method 2: With JSON filter
const params = new URLSearchParams({
'filter': JSON.stringify({
attributes: { status: { $eq: 'pending' } }
}),
'page[number]': '1',
'page[size]': '50'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
### Pagination Parameters
| Parameter | Type | Default | Max | Description |
| - | - | - | - | - |
| `page[number]` | integer | 1 | - | Page number (starts at 1) |
| `page[size]` | integer | 10 | 100 | Number of results per page |
The maximum `page[size]` is **100**. If you need to retrieve more results, use multiple paginated requests.
### Response Format
All paginated endpoints return results in the same format:
```json theme={null}
{
"meta": {
"total_count": 150,
"total_pages": 15,
"page_size": 10,
"current_page": 1
},
"data": [
{ "id": "abc-123", "title": "Case 1", "created_at": "2024-01-15T10:00:00Z" },
{ "id": "def-456", "title": "Case 2", "created_at": "2024-01-14T09:30:00Z" }
]
}
```
**Response Fields:**
| Field | Type | Description |
| - | - | - |
| `meta.total_count` | integer | Total number of results matching your filters |
| `meta.total_pages` | integer | Total number of pages available |
| `meta.page_size` | integer | Number of results per page |
| `meta.current_page` | integer | Current page number |
| `data` | array | Array of resource objects |
### Pagination Examples
#### Iterating Through Pages
```javascript theme={null}
async function getAllCases(workspaceSlug) {
const allCases = [];
let currentPage = 1;
let hasMorePages = true;
while (hasMorePages) {
const params = new URLSearchParams({
'workspace[slug]': workspaceSlug,
'page[number]': currentPage.toString(),
'page[size]': '100' // Max page size for efficiency
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const result = await response.json();
allCases.push(...result.data);
// Check if there are more pages
hasMorePages = currentPage < result.meta.total_pages;
currentPage++;
}
return allCases;
}
```
#### Loading a Specific Page
```javascript theme={null}
async function getCasesPage(pageNumber, pageSize = 25) {
const params = new URLSearchParams({
'page[number]': pageNumber.toString(),
'page[size]': pageSize.toString(),
'archived': 'false'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
```
#### Pagination with Filters
```javascript theme={null}
async function getPendingCasesPaginated(page = 1) {
const filter = {
attributes: {
parent_status: { $eq: 'pending' },
archived_at: { $eq: null }
}
};
const params = new URLSearchParams({
'filter': JSON.stringify(filter),
'page[number]': page.toString(),
'page[size]': '50'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
```
## Sorting
Control the order of results with the `sort` parameter. Sorting works with both direct parameters and JSON filters.
### Basic Usage
```bash theme={null}
# Sort by creation date (descending - newest first)
GET /v1/cases?sort=-created_at
# Sort by title (ascending - A to Z)
GET /v1/case_templates?sort=title
# Combine with filters
GET /v1/cases?workspace[slug]=acme&sort=-updated_at
# Sort with pagination
GET /v1/forms?sort=-created_at&page[size]=50
```
### Sort Format
| Format | Description | Example |
| - | - | - |
| `sort=field_name` | Ascending order (A-Z, oldest-newest, low-high) | `sort=title` |
| `sort=-field_name` | Descending order (Z-A, newest-oldest, high-low) | `sort=-created_at` |
Prefix the field name with a minus sign (`-`) for descending order.
### Common Sort Fields by Endpoint
#### Cases (`/v1/cases`)
* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* `title` - Case title
* `status` - Custom status value
* `parent_status` - Parent status
* `waiting_for` - Who the case is waiting for
* `reference` - Custom reference number
* `archived_at` - Archive timestamp
#### Case Templates (`/v1/case_templates`)
* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* `title` - Template title
* `reference` - Custom reference
* `archived_at` - Archive timestamp
#### Forms (`/v1/forms`)
* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
* Various custom fields based on form template
#### Form Templates (`/v1/form_templates`)
* `created_at` - Creation timestamp
* `updated_at` - Last update timestamp
### Sorting Examples
#### Sort by Date
```javascript theme={null}
// Get newest cases first
const params = new URLSearchParams({
'sort': '-created_at',
'page[size]': '25'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
#### Sort by Title
```javascript theme={null}
// Get case templates alphabetically
const params = new URLSearchParams({
'sort': 'title',
'archived': 'false'
});
const response = await fetch(
`https://connect.penbox.io/v1/case_templates?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
#### Sort with JSON Filter
```javascript theme={null}
// Get active cases sorted by last update
const filter = {
attributes: {
parent_status: { $eq: 'in_progress' },
archived_at: { $eq: null }
}
};
const params = new URLSearchParams({
'filter': JSON.stringify(filter),
'sort': '-updated_at'
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
```
#### Dynamic Sorting
```javascript theme={null}
function getCases(sortField, sortDirection = 'asc') {
const sortParam = sortDirection === 'desc'
? `-${sortField}`
: sortField;
const params = new URLSearchParams({
'sort': sortParam,
'page[size]': '50'
});
return fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
).then(r => r.json());
}
// Usage
const newestFirst = await getCases('created_at', 'desc');
const alphabetical = await getCases('title', 'asc');
```
## Combining Pagination and Sorting
Pagination and sorting work together seamlessly:
```javascript theme={null}
async function getCasesPaginatedAndSorted(options = {}) {
const {
page = 1,
pageSize = 25,
sortBy = 'created_at',
sortDirection = 'desc',
workspaceSlug = null
} = options;
const params = new URLSearchParams({
'page[number]': page.toString(),
'page[size]': pageSize.toString(),
'sort': sortDirection === 'desc' ? `-${sortBy}` : sortBy
});
if (workspaceSlug) {
params.set('workspace[slug]', workspaceSlug);
}
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
return await response.json();
}
// Usage examples
const firstPage = await getCasesPaginatedAndSorted({
page: 1,
pageSize: 50,
sortBy: 'title',
sortDirection: 'asc'
});
const recentUpdates = await getCasesPaginatedAndSorted({
page: 1,
sortBy: 'updated_at',
sortDirection: 'desc',
workspaceSlug: 'acme-corp'
});
```
## Best Practices
* Use larger page sizes (50-100) for batch processing
* Use smaller page sizes (10-25) for UI display
* Consider network latency and processing time
Never assume a dataset is small. Always implement pagination to avoid:
* API timeouts
* Memory issues
* Poor user experience
Sorting by `created_at` or `updated_at` is typically faster than custom fields. Use these when possible for better performance.
If sorting by the same field repeatedly, consider caching the results to reduce API calls.
Always check if the `data` array is empty:
```javascript theme={null}
const result = await response.json();
if (result.data.length === 0) {
console.log('No results found');
}
```
Show users how many total results exist:
```javascript theme={null}
const { meta, data } = await response.json();
console.log(`Showing ${data.length} of ${meta.total_count} results`);
```
## Complete Example: Data Table
Here's a complete example of implementing a paginated, sortable data table:
```javascript theme={null}
class CasesTable {
constructor(token, workspaceSlug) {
this.token = token;
this.workspaceSlug = workspaceSlug;
this.currentPage = 1;
this.pageSize = 25;
this.sortBy = 'created_at';
this.sortDirection = 'desc';
}
async fetchCases() {
const params = new URLSearchParams({
'workspace[slug]': this.workspaceSlug,
'page[number]': this.currentPage.toString(),
'page[size]': this.pageSize.toString(),
'sort': this.sortDirection === 'desc'
? `-${this.sortBy}`
: this.sortBy
});
const response = await fetch(
`https://connect.penbox.io/v1/cases?${params}`,
{ headers: { 'Authorization': `Bearer ${this.token}` } }
);
return await response.json();
}
async goToPage(pageNumber) {
this.currentPage = pageNumber;
return await this.fetchCases();
}
async nextPage() {
this.currentPage++;
return await this.fetchCases();
}
async previousPage() {
if (this.currentPage > 1) {
this.currentPage--;
}
return await this.fetchCases();
}
async sortBy(field, direction = 'asc') {
this.sortBy = field;
this.sortDirection = direction;
this.currentPage = 1; // Reset to first page when sorting
return await this.fetchCases();
}
async changePageSize(size) {
this.pageSize = Math.min(size, 100); // Enforce max
this.currentPage = 1; // Reset to first page
return await this.fetchCases();
}
}
// Usage
const table = new CasesTable(accessToken, 'acme-corp');
// Initial load
const firstPage = await table.fetchCases();
console.log(`Showing ${firstPage.data.length} of ${firstPage.meta.total_count} cases`);
// Navigate
await table.nextPage();
await table.goToPage(5);
// Sort
await table.sortBy('title', 'asc');
await table.sortBy('updated_at', 'desc');
// Change page size
await table.changePageSize(50);
```
## Next Steps
Learn how to filter results with operators
Apply pagination to the Cases endpoint
Paginate and sort forms
See all available sort fields
# API Overview
Source: https://docs.penbox.io/api-reference/pen-connect-overview
Complete guide to the Penbox API endpoints and capabilities
The Penbox API provides programmatic access to Penbox's form management, case management, and document processing capabilities. This comprehensive guide covers all API capabilities, response formats, and best practices.
Start with the Quick Start guide to make your first API call
## API Categories
### Cases
Manage multi-step workflows with contacts and structured data.
**Key Endpoints:**
* `GET /cases` - List cases with filtering
* `POST /cases` - Create a new case
* `GET /cases/{id}` - Get case details with steps
* `POST /cases/{id}/data` - Update case data fields
* `POST /cases/{id}` - Archive or unarchive a case
* `GET /cases/count` - Count cases
**Common Use Cases:**
* Create structured workflows for customer onboarding, support tickets, etc.
* Track case progress through multiple steps
* Manage contacts associated with cases
* Update case data programmatically
### Forms
Manage forms and responses.
**Key Endpoints:**
* `GET /forms` - List forms with filtering and pagination
* `POST /forms` - Create a new form
* `GET /forms/{id}` - Get form details including responses
* `PATCH /forms/{id}` - Update form status or properties
* `GET /forms/count` - Count forms with optional grouping
* `GET /responses/{id}` - Get response details
**Common Use Cases:**
* Send forms to customers or partners
* Track form completion status
* Archive or process completed forms
* Retrieve form responses with attachments
### Form Templates
Access and configure form templates and their customizations.
**Key Endpoints:**
* `GET /form_templates` - List available form templates
* `GET /form_templates/{slug}/options` - Get template configuration options
* `GET /form_templates/{slug}/steps` - Get template structure and fields
**Common Use Cases:**
* Discover available form templates
* Understand form structure before creating forms
* Access template-specific settings
### Attachments
Upload, download, and manage file attachments.
**Key Endpoints:**
* `POST /attachments` - Upload files (JSON or multipart)
* `GET /attachments/{id}` - Download single attachment
* `GET /attachments?ids=...` - Download multiple attachments as PDF
**Common Use Cases:**
* Upload documents for processing
* Download form response attachments
* Merge multiple documents into a single PDF
### Document Intelligence
Extract structured data from documents using AI.
**Key Endpoints:**
* `POST /document-intelligence` - Create extraction job
* `GET /document-intelligence/{id}` - Get extraction results
**Common Use Cases:**
* Extract invoice data (number, date, amount)
* Parse ID documents
* Process contracts and forms automatically
### Signatures
Access digital signatures created through form responses.
**Key Endpoints:**
* `GET /signatures/{id}` - Get signature metadata or download file
**Common Use Cases:**
* Download signed documents
* Track signature status
* Verify signature completion
### Workspaces
List available workspaces accessible with your token.
**Key Endpoints:**
* `GET /workspaces` - List accessible workspaces
## Rate Limiting
* **100 requests per minute** per access token
* **1,000 requests per hour** per client
If you exceed the rate limit, you'll receive a `429 Too Many Requests` response. Implement exponential backoff in your integration.
## Response Format
All responses use JSON format with consistent structure:
### Single Resource Response
```json theme={null}
{
"id": "uuid",
"created_at": "2024-01-15T10:30:00Z",
"status": "completed",
"archived": false,
...
}
```
### Paginated Collection Response
```json theme={null}
{
"meta": {
"total_count": 150,
"total_pages": 8,
"page_size": 20,
"current_page": 1
},
"data": [
{ "id": "uuid-1", ... },
{ "id": "uuid-2", ... }
]
}
```
### Error Response
```json theme={null}
{
"error": "Error message",
"statusCode": 400,
"message": "Detailed error description"
}
```
## Pagination and Filtering
The API provides powerful pagination, sorting, and filtering capabilities on all list endpoints.
Control result size and order with `page[number]`, `page[size]`, and `sort` parameters
Filter results with operators like `$eq`, `$ne`, `$gt`, `$like`, and `$extends`
## Common Headers
### Request Headers
* `Authorization: Bearer {token}` - Required for authentication
* `Content-Type: application/json` - For JSON request bodies
* `Accept: application/json` - Default response format (optional)
### Response Headers
* `Content-Type: application/json` - JSON responses
* `Content-Type: application/octet-stream` - Binary file downloads
## HTTP Status Codes
| Code | Description |
| - | - |
| `200` | Success |
| `201` | Created |
| `202` | Accepted (async processing) |
| `400` | Bad Request - Invalid input |
| `401` | Unauthorized - Invalid or missing token |
| `403` | Forbidden - No access to resource |
| `404` | Not Found - Resource doesn't exist |
| `413` | Payload Too Large |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Internal Server Error |
## Webhooks
When creating forms, you can specify webhook URLs to receive real-time notifications about events:
```json theme={null}
{
"webhooks": {
"https://your-server.com/webhook": [
"responses:completed",
"forms:updated"
]
}
}
```
Available webhook events:
* `forms:created`, `forms:updated`, `forms:deleted`
* `forms:archived`, `forms:unarchived`
* `responses:created`, `responses:updated`, `responses:deleted`
* `responses:completed`
## Best Practices
### 1. Use Filtering Wisely
Apply filters to reduce data transfer and improve response times.
### 2. Implement Error Handling
Always handle potential error responses and implement retry logic with exponential backoff.
### 3. Store IDs, Not Full Objects
Store resource IDs and fetch fresh data when needed to avoid stale data.
### 4. Use Webhooks for Real-Time Updates
Instead of polling, use webhooks to receive notifications when forms are completed.
### 5. Batch Operations
When possible, use batch endpoints like `POST /cases/{id}/data` to update multiple fields at once.
### 6. Respect Rate Limits
Implement proper rate limiting in your application to avoid `429` errors.
## Support
For questions or issues with the API:
* Check the detailed endpoint documentation
* Review the [Advanced Filters](/api-reference/advanced-filters) guide
* Contact support at [support@penbox.io](mailto:support@penbox.io)
## Version History
**Current Version:** v1
The API follows semantic versioning principles. Breaking changes will be introduced in new major versions.
# Count Requests
Source: https://docs.penbox.io/api-reference/previous-api-version/count-requests
Previous version of Count Forms. Count requests matching filter criteria.
Previous API version. Use [`GET /forms/count`](/api-reference/requests/count-requests) for new integrations. This route still works but is deprecated.
Get the total count of requests matching your filter criteria, with optional grouping by field values.
```text theme={null}
GET https://connect.penbox.io/v1/requests/count
```
## Query Parameters
| Parameter | Type | Description |
| - | - | - |
| `filter` | string | JSON-encoded filter object (same keys as `GET /requests`) |
| `group-by` | string | Group counts by `status`, `user.{field}`, or `data.{field}` |
## Response
### Without grouping
```json theme={null}
{ "count": 42 }
```
### With grouping
```json theme={null}
{
"count": 42,
"by": {
"status": { "pending": 15, "completed": 20, "draft": 7 }
}
}
```
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/requests/count?group-by=status' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success |
| `400` | Bad Request — invalid filter or group-by |
| `401` | Unauthorized |
| `403` | Forbidden |
| `429` | Too Many Requests |
| `500` | Server Error |
# Create Request
Source: https://docs.penbox.io/api-reference/previous-api-version/create-request
Previous version of Create Form. Create a new request from a flow.
Previous API version. Use [`POST /forms`](/api-reference/requests/create-request) for new integrations. This route still works but is deprecated.
Create a new request (now called a form) from a flow (form template).
```text theme={null}
POST https://connect.penbox.io/v1/requests
```
## Request Body
### Required
| Field | Type | Description |
| - | - | - |
| `flow.slug` | string | Flow (form template) identifier |
### Optional
| Field | Type | Description |
| - | - | - |
| `company.slug` | string | Workspace identifier (if multiple authorized) |
| `user.email` | string | Contact email address |
| `user.phone` | string | Contact phone number |
| `user.given_name` | string | Contact first name |
| `user.family_name` | string | Contact last name |
| `user.locale` | string | Contact language (e.g. `en`, `fr`) |
| `data` | object | Pre-filled data (keys must match field keys) |
| `options` | object | Request-specific options |
| `external_args` | object | Your custom metadata |
| `redirect_url` | string | HTTPS URL to redirect after completion |
| `draft` | boolean | Create as draft (default: `false`) |
| `active_from` | string | ISO 8601 date when the request becomes active |
| `active_until` | string | ISO 8601 date when the request expires |
| `owner.email` | string | Request owner email (or `null`) |
| `branding.slug` | string | Branding profile slug |
| `webhooks` | object | Map of HTTPS URL → array of subscribed events |
Supported webhook events: `requests:created`, `requests:updated`, `requests:deleted`, `requests:archived`, `requests:unarchived`, `responses:created`, `responses:updated`, `responses:deleted`, `responses:completed`.
## Example
```bash cURL theme={null}
curl -X POST 'https://connect.penbox.io/v1/requests' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"flow": { "slug": "client-onboarding" },
"user": { "email": "client@example.com", "given_name": "John", "family_name": "Doe" },
"data": { "company_name": "Acme Corporation" },
"external_args": { "crm_id": "12345" }
}'
```
## Response
Returns the created request object (same shape as [Get Request](/api-reference/previous-api-version/get-request)).
## Response Codes
| Code | Description |
| - | - |
| `201` | Created |
| `400` | Bad Request — invalid parameters |
| `401` | Unauthorized |
| `403` | Forbidden |
| `404` | Not Found — flow not enabled for the user |
| `409` | Conflict — more than one flow matches |
| `422` | Unprocessable — validation failed |
| `429` | Too Many Requests |
| `500` | Server Error |
# Get Flow Options
Source: https://docs.penbox.io/api-reference/previous-api-version/get-flow-options
Previous version of Get Form Template Options.
Previous API version. Use [`GET /form_templates/{slug}/options`](/api-reference/flows/get-flow-options) for new integrations. This route still works but is deprecated.
Retrieve the configuration options for a specific flow (now called a form template).
```text theme={null}
GET https://connect.penbox.io/v1/flows/{slug}/options
```
## Path Parameters
| Parameter | Type | Description |
| - | - | - |
| `slug` | string | Flow identifier |
The exact structure of the options object depends on the flow configuration. Different flows may expose different options.
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/flows/client-onboarding/options' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success |
| `401` | Unauthorized |
| `403` | Forbidden |
| `404` | Not Found — flow not found or not enabled |
| `429` | Too Many Requests |
| `500` | Server Error |
# Get Flow Steps
Source: https://docs.penbox.io/api-reference/previous-api-version/get-flow-steps
Previous version of Get Form Template Steps.
Previous API version. Use [`GET /form_templates/{slug}/steps`](/api-reference/flows/get-flow-steps) for new integrations. This route still works but is deprecated.
Retrieve the steps/structure of a specific flow (now called a form template).
```text theme={null}
GET https://connect.penbox.io/v1/flows/{slug}/steps
```
## Path Parameters
| Parameter | Type | Description |
| - | - | - |
| `slug` | string | Flow identifier |
Use this endpoint to understand the structure of a flow before creating requests, or to build a custom form UI.
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/flows/client-onboarding/steps' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success |
| `401` | Unauthorized |
| `403` | Forbidden |
| `404` | Not Found — flow not found or not enabled |
| `429` | Too Many Requests |
| `500` | Server Error |
# Get Request
Source: https://docs.penbox.io/api-reference/previous-api-version/get-request
Previous version of Get Form. Retrieve a single request with responses and notifications.
Previous API version. Use [`GET /forms/{id}`](/api-reference/requests/get-request) for new integrations. This route still works but is deprecated.
Retrieve complete details of a specific request (now called a form), including responses, attachments, and notifications.
```text theme={null}
GET https://connect.penbox.io/v1/requests/{id}
```
## Path Parameters
| Parameter | Type | Description |
| - | - | - |
| `id` | string (uuid) | Request identifier |
## Response
Returns a single request object:
```json theme={null}
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2024-01-15T10:30:00Z",
"status": "completed",
"archived": false,
"user": { "email": "client@example.com", "given_name": "John", "family_name": "Doe" },
"external_args": { "crm_id": "12345" },
"flow": { "slug": "client-onboarding" },
"owner": null,
"active_from": "2024-01-15T10:30:00Z",
"active_until": null,
"archived_at": null,
"processed_at": null,
"workspace": { "id": "...", "slug": "acme-corp" },
"links": { "fill": "https://...", "app": "https://..." },
"responses": [
{ "id": "...", "completed_at": "2024-01-16T09:00:00Z", "user": {}, "attachments": [] }
],
"notifications": []
}
```
The `responses` and `notifications` arrays carry the same fields as the current [Get Form](/api-reference/requests/get-request) endpoint.
## Response Codes
| Code | Description |
| - | - |
| `200` | Success |
| `401` | Unauthorized |
| `403` | Forbidden |
| `404` | Not Found — request doesn't exist |
| `429` | Too Many Requests |
| `500` | Server Error |
# List Companies
Source: https://docs.penbox.io/api-reference/previous-api-version/list-companies
Previous version of List Workspaces. Retrieve authorized companies.
Previous API version. Use [`GET /workspaces`](/api-reference/workspaces/list-workspaces) for new integrations. This route still works but is deprecated.
Retrieve all companies (now called workspaces) that the authenticated user has authorized your application to access.
```text theme={null}
GET https://connect.penbox.io/v1/companies
```
## Response
Unlike the current `GET /workspaces` endpoint, this route returns a **bare JSON array** with no pagination `meta` envelope.
```json theme={null}
[
{
"id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"created_at": "2024-01-15T10:30:00Z",
"slug": "acme-corp",
"name": "Acme Corp"
}
]
```
The `slug` is a URL-safe, unique identifier used in API calls to specify which workspace you want to interact with.
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/companies' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success — returns array of companies |
| `401` | Unauthorized |
| `403` | Forbidden |
| `429` | Too Many Requests |
| `500` | Server Error |
# List Flows
Source: https://docs.penbox.io/api-reference/previous-api-version/list-flows
Previous version of List Form Templates. Retrieve available flows.
Previous API version. Use [`GET /form_templates`](/api-reference/flows/list-flows) for new integrations. This route still works but is deprecated.
Retrieve all active flows (now called form templates) available in the authorized workspaces.
```text theme={null}
GET https://connect.penbox.io/v1/flows
```
## Query Parameters
| Parameter | Type | Description |
| - | - | - |
| `archived` | boolean | When `true`, returns archived flows instead of active ones |
## Response
Unlike the current `GET /form_templates` endpoint, this route returns a **bare JSON array** with no pagination `meta` envelope.
```json theme={null}
[
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"created_at": "2024-01-15T10:30:00Z",
"slug": "client-onboarding",
"name": "Client Onboarding",
"archived_at": null,
"enabled": true,
"workspace": { "id": "...", "slug": "acme-corp" }
}
]
```
Flow slugs can be reused across different workspaces. Always combine the flow slug with the workspace slug when storing or referencing them in your system.
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/flows' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success — returns array of flows |
| `401` | Unauthorized |
| `403` | Forbidden |
| `429` | Too Many Requests |
| `500` | Server Error |
# List Requests
Source: https://docs.penbox.io/api-reference/previous-api-version/list-requests
Previous version of List Forms. Retrieve a list of requests with filtering.
Previous API version. Use [`GET /forms`](/api-reference/requests/list-requests) for new integrations. This route still works but is deprecated.
Retrieve a list of requests (now called forms) matching specific criteria.
```text theme={null}
GET https://connect.penbox.io/v1/requests
```
## Query Parameters
| Parameter | Type | Description |
| - | - | - |
| `filter` | string | JSON-encoded filter object (workspace, flow, user, owner, status, external args) |
| `page[number]` | number | Page number (starts at 1) |
| `page[size]` | number | Results per page (default: 10, max: 100) |
The `filter` object accepts the same keys as the current endpoint: `company.slug`, `flow.slug`, `user`, `owner.email`, `active`, `archived`, `completed`, `processed`, and `external_args`. See the [Advanced Filters guide](/api-reference/advanced-filters).
## Response
Unlike the current `GET /forms` endpoint, this route returns a **bare JSON array** with no pagination `meta` envelope.
```json theme={null}
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"created_at": "2024-01-15T10:30:00Z",
"status": "pending",
"archived": false,
"user": {
"email": "client@example.com",
"given_name": "John",
"family_name": "Doe"
},
"external_args": { "crm_id": "12345" },
"flow": { "slug": "client-onboarding" },
"owner": null,
"active_from": "2024-01-15T10:30:00Z",
"active_until": null,
"archived_at": null,
"processed_at": null,
"workspace": { "id": "...", "slug": "acme-corp" },
"links": { "fill": "https://...", "app": "https://..." },
"responses": []
}
]
```
## Example
```bash cURL theme={null}
curl 'https://connect.penbox.io/v1/requests' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success — returns array of requests |
| `400` | Bad Request — invalid filter parameters |
| `401` | Unauthorized — invalid or expired access token |
| `403` | Forbidden — token lacks required permissions |
| `429` | Too Many Requests — rate limit exceeded |
| `500` | Server Error |
# Overview
Source: https://docs.penbox.io/api-reference/previous-api-version/overview
Previous-generation Connect API routes (requests, flows, companies) that remain available for backward compatibility.
Before the current Forms API, the Connect API exposed an earlier generation of routes built around `requests`, `flows`, and `companies`. These routes are **still served** for backward compatibility but are no longer part of the main API list above.
These endpoints are deprecated. They are maintained so existing integrations keep working, but they receive no new features and may be removed in a future major version. **New integrations should use the current endpoints** ([Forms](/api-reference/requests/list-requests), [Form Templates](/api-reference/flows/list-flows), [Workspaces](/api-reference/workspaces/list-workspaces)).
They use the same base URL and the same Bearer-token authentication as the current API:
```text theme={null}
https://connect.penbox.io/v1
```
```bash theme={null}
Authorization: Bearer YOUR_ACCESS_TOKEN
```
## Endpoint mapping
Each previous-version route has a direct equivalent in the current API. Request bodies, query filters, and per-item response shapes are identical — see the current endpoint for the full schema and the interactive "Try It" tool.
### Forms (previously `requests`)
| Previous route | Current equivalent |
| - | - |
| `GET /requests` | [`GET /forms`](/api-reference/requests/list-requests) |
| `GET /requests/count` | [`GET /forms/count`](/api-reference/requests/count-requests) |
| `POST /requests` | [`POST /forms`](/api-reference/requests/create-request) |
| `GET /requests/{id}` | [`GET /forms/{id}`](/api-reference/requests/get-request) |
| `PATCH /requests/{id}` | [`PATCH /forms/{id}`](/api-reference/requests/update-request) |
### Form Templates (previously `flows`)
| Previous route | Current equivalent |
| - | - |
| `GET /flows` | [`GET /form_templates`](/api-reference/flows/list-flows) |
| `GET /flows/{slug}/options` | [`GET /form_templates/{slug}/options`](/api-reference/flows/get-flow-options) |
| `GET /flows/{slug}/steps` | [`GET /form_templates/{slug}/steps`](/api-reference/flows/get-flow-steps) |
### Workspaces (previously `companies`)
| Previous route | Current equivalent |
| - | - |
| `GET /companies` | [`GET /workspaces`](/api-reference/workspaces/list-workspaces) |
## Key differences
Apart from the path, the only behavioral change is in the **list** endpoints.
* **List responses are not paginated.** The previous list routes (`GET /requests`, `GET /flows`, `GET /companies`) return a **bare JSON array**. The current routes wrap results in a pagination envelope (`{ "meta": { ... }, "data": [ ... ] }`).
* **Resource naming.** The previous routes use the older resource names — `requests` (now forms), `flows` (now form templates), and `companies` (now workspaces). The shape of each item is otherwise unchanged.
Unchanged, current-generation routes such as [`GET /responses/{id}`](/api-reference/responses/get-response), [`GET /attachments/{id}`](/api-reference/attachments/get-attachment), and [`GET /signatures/{id}`](/api-reference/signatures/get-signature) are documented in their own sections above and are **not** part of the previous API version.
# Update Request
Source: https://docs.penbox.io/api-reference/previous-api-version/update-request
Previous version of Update Form. Update a request's status or expiration.
Previous API version. Use [`PATCH /forms/{id}`](/api-reference/requests/update-request) for new integrations. This route still works but is deprecated.
Update a request's (now form's) status or expiration date.
```text theme={null}
PATCH https://connect.penbox.io/v1/requests/{id}
```
## Path Parameters
| Parameter | Type | Description |
| - | - | - |
| `id` | string (uuid) | Request identifier |
## Request Body
At least one field must be provided.
| Field | Type | Description |
| - | - | - |
| `archived` | boolean | Archive or unarchive the request |
| `draft` | boolean | Set draft status (`true` = draft, `false` = activate) |
| `processed` | boolean | Mark as processed or unprocessed |
| `active_until` | string | Update expiration date (ISO 8601) |
## Example
```bash cURL theme={null}
curl -X PATCH 'https://connect.penbox.io/v1/requests/REQUEST_ID' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{ "archived": true }'
```
## Response
Returns the updated request object (same shape as [Get Request](/api-reference/previous-api-version/get-request)).
## Response Codes
| Code | Description |
| - | - |
| `200` | Success |
| `400` | Bad Request — no valid field provided |
| `401` | Unauthorized |
| `403` | Forbidden |
| `404` | Not Found — request doesn't exist |
| `422` | Unprocessable — validation failed |
| `429` | Too Many Requests |
| `500` | Server Error |
# Count Forms
Source: https://docs.penbox.io/api-reference/requests/count-requests
GET /forms/count
Get the total count of forms matching filter criteria, with optional grouping.
Get the total count of forms matching your filter criteria, with optional grouping by field values.
## Query Parameters
Group counts by a specific field. Supported values:
* `status` - Group by form status
* `user.{field}` - Group by user field (e.g., `user.email`)
* `data.{field}` - Group by data field (e.g., `data.property_type`)
## Response Structure
### Without Grouping
```json theme={null}
{
"count": 42
}
```
### With Grouping
```json theme={null}
{
"count": 42,
"by": {
"status": {
"pending": 15,
"completed": 20,
"draft": 7
}
}
}
```
Use the `group-by` parameter to get distribution statistics for your forms, which is useful for dashboards and analytics.
# Create Form
Source: https://docs.penbox.io/api-reference/requests/create-request
POST /forms
Create a new form to send to a contact.
## Request Parameters
### Required Fields
| Field | Type | Description |
| - | - | - |
| `flow.slug` | string | Form template identifier |
### Optional Fields
| Field | Type | Description |
| - | - | - |
| `company.slug` | string | Workspace identifier (if multiple authorized) |
| `user.email` | string | Contact email address |
| `user.phone` | string | Contact phone number |
| `user.given_name` | string | Contact first name |
| `user.family_name` | string | Contact last name |
| `user.locale` | string | Contact language (e.g., `en`, `fr`) |
| `data` | object | Pre-filled form data (keys must match form field keys) |
| `external_args` | object | Your custom metadata |
| `redirect_url` | string | HTTPS URL to redirect after completion |
| `draft` | boolean | Create as draft (default: `false`) |
| `active_from` | string | ISO 8601 date when form becomes active |
| `active_until` | string | ISO 8601 date when form expires |
| `owner.email` | string | Form owner email address |
| `branding.slug` | string | Branding profile slug (see [Branding](#branding) below) |
## Form Statuses
| Status | Description |
| - | - |
| `draft` | Form created but not yet sent |
| `pending` | Form sent, waiting for completion |
| `completed` | Contact submitted the form |
| `declined` | Contact declined to fill the form |
| `processed` | Response has been reviewed/processed |
## Pre-filling Form Data
The `data` parameter allows you to pre-fill form fields with values. This is useful for importing data from your system or providing a better user experience by reducing the amount of information the contact needs to enter.
### How It Works
The keys in the `data` object **must exactly match the field keys** defined in your form template. When a contact opens the form, these fields will already contain the provided values.
**Example:**
If your form template has fields with keys:
* `company_name`
* `address_zip`
* `annual_revenue`
You can pre-fill them like this:
```json theme={null}
{
"data": {
"company_name": "Acme Corp",
"address_zip": "10001",
"annual_revenue": "500000"
}
}
```
### Finding Field Keys
Field keys are defined in your form template configuration in Penbox. Common examples:
| Field Type | Example Keys |
| - | - |
| **Company Info** | `company_name`, `company_vat`, `company_registration` |
| **Address** | `address_street`, `address_city`, `address_zip`, `address_country` |
| **Contact** | `contact_name`, `contact_email`, `contact_phone` |
| **Financial** | `annual_revenue`, `number_employees`, `bank_account` |
| **Custom** | Any custom field keys you've defined in your template |
Field keys are case-sensitive and must match exactly. Use the Penbox app or contact your administrator to find the correct field keys for your form template.
### Complete Example
```javascript theme={null}
const response = await fetch('https://connect.penbox.io/v1/forms', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
flow: { slug: 'client-onboarding' },
user: {
email: 'client@example.com',
given_name: 'John',
family_name: 'Doe'
},
// Pre-fill form fields
data: {
company_name: 'Acme Corporation',
company_vat: 'BE0123456789',
address_street: '123 Main Street',
address_city: 'New York',
address_zip: '10001',
address_country: 'US',
annual_revenue: '1500000',
number_employees: '25'
},
external_args: {
crm_id: '12345'
},
branding: {
slug: 'partner-brand'
}
})
});
```
```bash cURL theme={null}
curl -X POST 'https://connect.penbox.io/v1/forms' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"flow": { "slug": "client-onboarding" },
"user": {
"email": "client@example.com",
"given_name": "John",
"family_name": "Doe"
},
"data": {
"company_name": "Acme Corporation",
"company_vat": "BE0123456789",
"address_street": "123 Main Street",
"address_city": "New York",
"address_zip": "10001",
"address_country": "US",
"annual_revenue": "1500000",
"number_employees": "25"
},
"external_args": {
"crm_id": "12345"
},
"branding": {
"slug": "partner-brand"
}
}'
```
### Use Cases
**Import from CRM:**
```javascript theme={null}
// Pull data from your CRM and pre-fill the form
const crmContact = await getCRMContact(contactId);
const formData = {
company_name: crmContact.companyName,
address_zip: crmContact.zipCode,
annual_revenue: crmContact.revenue,
contact_phone: crmContact.phone
};
await createForm({
flow: { slug: 'onboarding' },
user: { email: crmContact.email },
data: formData
});
```
**Progressive Forms:**
```javascript theme={null}
// Pre-fill with data collected in previous steps
const formData = {
company_name: step1Data.company,
address_zip: step2Data.zip,
business_type: step3Data.type
};
```
**Reduce Friction:**
```javascript theme={null}
// Pre-fill known information to make the form faster
const formData = {
company_name: user.organization,
contact_email: user.email,
contact_phone: user.phone
};
```
Pre-filling data improves form completion rates and reduces errors. Always pre-fill what you know while allowing contacts to modify values if needed.
If you provide a field key that doesn't exist in the form template, it will be silently ignored. Make sure your field keys match your template configuration.
## Branding
Use the `branding` parameter to apply a specific visual identity to a form — logo, colors, favicon, and related styling in the form experience and email notifications.
### Default behavior
When `branding` is omitted, the form uses your workspace's default branding — the profile configured under **Settings → Branding** in Penbox.
### How it works
Pass the slug of an existing branding profile in your workspace:
```json theme={null}
{
"flow": { "slug": "client-onboarding" },
"user": { "email": "client@example.com" },
"branding": {
"slug": "partner-brand"
}
}
```
The slug must match a branding profile that already exists in the workspace. If the slug is invalid or not found, the API returns `404`.
To use the workspace default branding, omit the `branding` field entirely. The slug `default` is only a UI label for the primary workspace profile — it is not a valid API value.
### Finding the branding slug
1. Open **Settings → Branding** in Penbox.
2. If your workspace has multiple branding profiles, select the profile you want and note its slug — it is displayed next to the profile selector (for example, `slug: partner-brand`).
See [Branding](/workspace/settings/branding) for how to create and manage branding profiles.
### Use cases
**White-label partner forms:**
```javascript theme={null}
await createForm({
flow: { slug: 'onboarding' },
user: { email: partner.contactEmail },
branding: { slug: 'partner-acme' }
});
```
**Different brands per client segment:**
```javascript theme={null}
const brandingSlug = client.segment === 'enterprise'
? 'enterprise-brand'
: 'self-serve-brand';
await createForm({
flow: { slug: 'signup' },
user: { email: client.email },
branding: { slug: brandingSlug }
});
```
Branding is set at form creation and applies to the form link and related emails for that form. Create separate branding profiles in Penbox when you need distinct visual identities for different clients or product lines.
## External Args
Use `external_args` to store your own metadata with forms for easy tracking:
```json theme={null}
{
"external_args": {
"crm_id": "12345",
"order_id": "ORD-789",
"source": "website",
"campaign": "summer-2024"
}
}
```
You can filter by external\_args when listing forms:
```json theme={null}
{
"filter": {
"external_args": {
"order_id": "ORD-789"
}
}
}
```
## Response Codes
| Code | Description |
| - | - |
| `201` | Created - New form created successfully |
| `400` | Bad Request - Invalid parameters |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No access to this resource |
| `409` | Conflict - Multiple form templates match criteria |
| `422` | Unprocessable - Validation failed |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Created forms are immediately active unless `draft: true` is specified. Draft forms must be activated by setting `draft: false` via PATCH.
Use `external_args` to link Penbox forms back to your system's entities (orders, customers, tickets, etc.). This makes it easy to track and manage forms in your system.
The `active_from` and `active_until` dates control when the form link is accessible. Set `active_until` to create expiring forms.
# Get Form
Source: https://docs.penbox.io/api-reference/requests/get-request
GET /forms/{id}
Retrieve details of a specific form by ID.
Retrieve complete details of a specific form including responses and attachments.
## Response Structure
The response includes complete form details with all nested data:
### Form Fields
* **id**: Form UUID
* **created\_at**: Form creation timestamp
* **status**: Current form status (draft, pending, completed, declined, processed)
* **archived**: Boolean indicating if archived
* **archived\_at**: Timestamp when archived (if applicable)
* **processed\_at**: Timestamp when marked as processed (if applicable)
* **active\_from**: When the form becomes active (if set)
* **active\_until**: When the form expires (if set)
* **links.fill**: Public form link for the contact
* **links.app**: Internal app link to view the form
* **user**: Contact information (email, name, phone, anonymous flag)
* **owner**: Form owner information (id, email) if applicable
* **flow**: Form template info with slug
* **data**: Pre-filled form data object
* **external\_args**: Your custom metadata object
* **options**: Form-specific options
* **responses**: Array of response objects with attachments and signatures
* **notifications**: Array of notification objects
### Notification Fields
Each notification object contains:
* **id**: Unique identifier for the notification (UUID)
* **status**: Current status of the notification (`pending`, `sent`, `failed`, `skipped`)
* **method**: Delivery method (`sms` or `email`)
* **at**: Timestamp when the notification was sent (ISO 8601 format)
* **to**: Recipient address (email address or phone number), can be null
* **from**: Sender address (email or system identifier), can be null
* **cc**: Carbon copy recipient(s), can be null
* **bcc**: Blind carbon copy recipient(s), can be null
* **system**: Boolean indicating if this is a system notification
* **locale**: Language/locale code (e.g., 'fr', 'en')
* **template**: Template name used for the notification (e.g., 'raw', 'request\_completed')
* **variables**: Object containing template variables used in the message, can be null
* **active**: Boolean indicating if the notification is active
* **error**: Error message if notification failed, can be null
* **message\_id**: External message identifier from the delivery provider, can be null
* **attachments**: Attached files, can be null
* **deleted\_at**: Timestamp when the notification was deleted, can be null
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - Form details retrieved |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No access to this resource |
| `404` | Not Found - Form doesn't exist |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
The response includes all related data nested within the form object (responses, notifications, etc.), so you don't need additional API calls to access related resources.
# List Forms
Source: https://docs.penbox.io/api-reference/requests/list-requests
GET /forms
Retrieve a paginated list of forms with optional filtering and sorting. By default, only non-archived forms are returned.
Retrieve a list of forms matching specific criteria. Use filters to narrow down results.
## Query Parameters
| Parameter | Type | Description |
| - | - | - |
| `filter` | string | JSON-encoded filter object |
| `page[number]` | number | Page number (starts at 1) |
| `page[size]` | number | Results per page (default: 10, max: 100) |
## Filter Options
You can filter forms using a JSON-encoded filter object. The Connect API supports filtering by workspace, template, user, status, and custom metadata.
**Quick Example:**
```json theme={null}
{
"flow": { "slug": "client-onboarding" },
"active": true,
"completed": false
}
```
**Available Filters:**
* **Workspace** (workspace slug)
* **Form Template** (form template slug)
* **User** (email, name, phone, internal\_ref, company\_name)
* **Owner** (email)
* **Status** (active, archived, completed, processed)
* **External args** (your custom metadata)
See the complete filtering documentation with examples and use cases
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - Returns list of forms |
| `400` | Bad Request - Invalid filter parameters |
| `401` | Unauthorized - Invalid or expired access token |
| `403` | Forbidden - Token doesn't have required permissions |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Use `page[number]` and `page[size]` for pagination when working with large datasets. The maximum page size is 100 forms per request.
# Update Form
Source: https://docs.penbox.io/api-reference/requests/update-request
PATCH /forms/{id}
Update a form's status or properties.
Update a form's status or properties.
## Updatable Fields
| Field | Type | Description |
| - | - | - |
| `archived` | boolean | Archive or unarchive the form |
| `draft` | boolean | Set draft status (true = draft, false = activate) |
| `processed` | boolean | Mark as processed or unprocessed |
| `active_until` | string | Update expiration date (ISO 8601) |
## Common Use Cases
### Archive a Form
Archive completed or cancelled forms to keep your list organized:
```javascript theme={null}
await fetch(`https://connect.penbox.io/v1/forms/${formId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ archived: true })
});
```
### Mark as Processed
Track which forms you've reviewed in your system:
```javascript theme={null}
await fetch(`https://connect.penbox.io/v1/forms/${formId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ processed: true })
});
```
### Activate a Draft
Activate a draft form to send it to the contact:
```javascript theme={null}
await fetch(`https://connect.penbox.io/v1/forms/${formId}`, {
method: 'PATCH',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ draft: false })
});
```
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - Form updated successfully |
| `400` | Bad Request - Invalid parameters |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No access to this resource |
| `404` | Not Found - Form doesn't exist |
| `422` | Unprocessable - Validation failed |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
You can only update certain fields. To modify form data or user information, you'll need to create a new form.
# Get Form Response
Source: https://docs.penbox.io/api-reference/responses/get-response
GET /responses/{id}
Retrieve complete response data including form answers and attachments.
Retrieve complete form response data including answers and attachments.
Form responses are called **responses** in the API. Each form (request) can have one or more responses as contacts fill them out.
## Response Formats
The endpoint supports two response formats controlled by the `Accept` header:
| Format | Accept Header | Description |
| - | - | - |
| **JSON** | `application/json` | Structured data including all form fields and file references |
| **PDF** | `application/pdf` | Generated PDF of the complete form response |
## Response Fields
### Response Attributes
| Field | Type | Description |
| - | - | - |
| `$created_at` | string | ISO 8601 timestamp when response was created |
| `$updated_at` | string | ISO 8601 timestamp of last update |
| `completed_at` | string/null | ISO 8601 timestamp when completed (null if pending) |
| `declined_at` | string/null | ISO 8601 timestamp when declined (null if not declined) |
| `user` | object | Contact information who filled the form |
| `data` | object | Form field values keyed by field name |
| `rating` | string | Optional rating provided by the contact |
### User Object
| Field | Type | Description |
| - | - | - |
| `email` | string | Contact email address |
| `given_name` | string | Contact first name |
| `family_name` | string | Contact last name |
| `phone` | string | Contact phone number |
| `locale` | string | Language used to fill the form (e.g., `en`, `fr`) |
## Completion Status
| Status | Description | Indicators |
| - | - | - |
| **Pending** | Form started but not submitted | `completed_at: null`, `declined_at: null` |
| **Completed** | Form successfully submitted | `completed_at` timestamp present, `declined_at: null` |
| **Declined** | Contact declined to fill the form | `completed_at: null`, `declined_at` timestamp present |
## Finding Response IDs
Response IDs are included when you retrieve a form. Use the relationships to find responses:
```javascript theme={null}
// Get a form with its responses
const requestResponse = await fetch(
`https://connect.penbox.io/v1/requests/${requestId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const { data, included } = await requestResponse.json();
// Extract response IDs from relationships
const responseIds = data.relationships.responses.data.map(r => r.id);
// Fetch each response
for (const responseId of responseIds) {
const res = await fetch(
`https://connect.penbox.io/v1/responses/${responseId}`,
{
headers: { 'Authorization': `Bearer ${accessToken}` }
}
);
const responseData = await res.json();
console.log('Response data:', responseData.data.attributes.data);
}
```
## Accessing Form Data
The `data` attribute contains all form field values:
```javascript theme={null}
const { data } = await fetch(
`https://connect.penbox.io/v1/responses/${responseId}`,
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
).then(r => r.json());
const formData = data.attributes.data;
// Access specific fields
console.log('Company name:', formData.company_name);
console.log('Registration number:', formData.registration_number);
// Check completion status
const isCompleted = data.attributes.completed_at !== null;
const isDeclined = data.attributes.declined_at !== null;
```
## Working with Uploaded Files
Responses include references to uploaded files in the `included` array:
```javascript theme={null}
const { data, included } = await fetch(
`https://connect.penbox.io/v1/responses/${responseId}`,
{ headers: { 'Authorization': `Bearer ${accessToken}` } }
).then(r => r.json());
// Find files in the included array
const files = included.filter(item => item.type === 'attachments');
for (const file of files) {
console.log('File:', file.attributes.name);
console.log('Type:', file.attributes.type);
console.log('Size:', file.attributes.metadata.size);
// Download the file using the Files endpoint
const fileUrl = `https://connect.penbox.io/v1/attachments/${file.id}`;
}
```
See [Get File](/api-reference/attachments/get-attachment) for download details.
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - Response data retrieved |
| `401` | Unauthorized - Invalid access token |
| `403` | Forbidden - No access to this response |
| `404` | Not Found - Response doesn't exist |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Responses are automatically created when a contact starts filling out a form. Initially, they contain partial data and no completion timestamp.
Once a response is completed (`completed_at` is set), it becomes immutable. You cannot modify the data or user information.
# Get Signature
Source: https://docs.penbox.io/api-reference/signatures/get-signature
GET /signatures/{id}
Retrieve signature information or download the signature file.
Retrieve signature information or download the signature file.
## Path Parameters
Signature UUID
## Headers
Response format:
* `application/json` - Get signature metadata (default)
* `application/octet-stream` - Download signature file
## Response Structure (JSON)
When `Accept: application/json` is used:
```json theme={null}
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:35:00Z",
"name": "Contract Signature",
"key": "customer_signature",
"status": "completed",
"items": 3,
"method": "electronic",
"merge": true,
"cost": 2.50,
"locale": "en"
}
```
## Response Fields (JSON)
Signature UUID
Creation timestamp
Last update timestamp
Signature name
Signature identifier key
Signature status (e.g., "pending", "completed", "declined")
Number of items/pages to be signed
Signature method (e.g., "electronic", "qualified")
Whether to merge signatures into a single document
Signature cost (if applicable)
Language/locale code
## Response (Binary)
When `Accept: application/octet-stream` is used, returns the signature file (typically PDF format).
## Response Codes
| Code | Description |
| - | - |
| `200` | Success - Signature retrieved |
| `401` | Unauthorized - Invalid access token |
| `404` | Not Found - Signature doesn't exist |
| `429` | Too Many Requests - Rate limit exceeded |
| `500` | Server Error - Internal error |
Signatures are typically generated when a form response includes signature fields. The signature file contains the legally binding signed documents.
# Create Case Task
Source: https://docs.penbox.io/api-reference/tasks/create-case-task
POST /cases/{id}/tasks
Add a follow-up task to a case
Add a task to a case. Use it to put your own follow-ups next to the ones Penbox
creates, so the team sees a single to-do list.
The task lands on the **team side**: assign it to a workspace member by email, or
leave it unassigned. Assign by email and Penbox resolves the member for you —
including members you registered through
[Create Member](/api-reference/members/create-member), who never receive
Penbox emails.
You cannot create a task on the **client** side, nor move one there later. Those
are created by Penbox when it sends a form, and they close when the client submits
it — a client-side task you created would have no way to ever be closed.
The case and the assignee must both belong to a workspace your key covers.
Referencing either from another workspace returns `403`.
`type` is set to `manual` for every task you create. It is not a field you send:
Penbox reserves its own keys (`fill_form`, `review`, `follow_up`) for the tasks it
manages itself, and closes those automatically as the case moves.
```bash Assigned theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id}/tasks \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{
"title": "Call the client back",
"description": "The client asked for a callback before Friday.",
"due_at": "2026-09-04T00:00:00.000Z",
"assignee": { "email": "amina@firm.example" }
}'
```
```bash Unassigned theme={null}
curl --request POST \
--url https://connect.penbox.io/v1/cases/{id}/tasks \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "title": "Check the policy number" }'
```
```json 201 theme={null}
{
"id": "2c7e2ddd-ece1-4c38-9c2d-47aaaf50be97",
"created_at": "2026-08-31T10:12:04.100Z",
"case": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"type": "manual",
"title": "Call the client back",
"description": "The client asked for a callback before Friday.",
"status": "open",
"due_at": "2026-09-04T00:00:00.000Z",
"completed_at": null,
"step_key": null,
"assignee": {
"kind": "member",
"id": "5f7e2c17-e9be-47c9-8049-0b35d8eb0ada",
"email": "amina@firm.example",
"given_name": "Amina",
"family_name": "Berger"
}
}
```
```json 400 theme={null}
{
"statusCode": 400,
"error": "Bad Request",
"message": "Assignee with email \"nobody@firm.example\" not found in workspace"
}
```
# List Case Tasks
Source: https://docs.penbox.io/api-reference/tasks/list-case-tasks
GET /cases/{id}/tasks
List the follow-up tasks of a case
List the tasks of a case, oldest due date first.
A task is a follow-up: something one person has to do before the case can move on.
Every task belongs to exactly one **side**, told by `assignee.kind`:
| `assignee.kind` | Whose job it is | Closed by |
| - | - | - |
| `member` | someone on the team | the team, or your integration |
| `contact` | the client | submitting the form Penbox sent them |
| `null` (unassigned) | nobody yet | anyone on the team |
This list includes the tasks **Penbox creates by itself** when a form step moves —
"Complete the form" for the client, "Review the answers" for the team — so you can
mirror the real state of the case in your own product.
Tasks are scoped to the workspaces your token covers. A case in another workspace
returns an empty list rather than an error.
The response is not paginated and returns at most **100** tasks, oldest due date
first. A case is not expected to carry more.
```bash cURL theme={null}
curl --request GET \
--url https://connect.penbox.io/v1/cases/{id}/tasks \
--header 'Authorization: Bearer '
```
```json 200 theme={null}
{
"data": [
{
"id": "9b1c4b2e-1f3a-4f8c-9c2d-47aaaf50be97",
"created_at": "2026-08-31T09:54:39.963Z",
"case": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"type": "fill_form",
"title": null,
"description": null,
"status": "in_progress",
"due_at": "2026-09-07T00:00:00.000Z",
"completed_at": null,
"step_key": "ask_documents",
"assignee": {
"kind": "contact",
"id": "d322ce86-fb0a-4fc9-b45a-28758840108c",
"email": "client@example.com",
"given_name": "Judd",
"family_name": "Haley"
}
},
{
"id": "2c7e2ddd-ece1-4c38-9c2d-47aaaf50be97",
"created_at": "2026-08-31T10:12:04.100Z",
"case": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"type": "manual",
"title": "Call the client back",
"description": "The client asked for a callback before Friday.",
"status": "open",
"due_at": "2026-09-04T00:00:00.000Z",
"completed_at": null,
"step_key": null,
"assignee": {
"kind": "member",
"id": "5f7e2c17-e9be-47c9-8049-0b35d8eb0ada",
"email": "amina@firm.example",
"given_name": "Amina",
"family_name": "Berger"
}
}
]
}
```
# Update Case Task
Source: https://docs.penbox.io/api-reference/tasks/update-case-task
PATCH /cases/{id}/tasks/{taskId}
Close, reopen, or reassign a follow-up task
Update a task of a case. Send `status` to close or reopen it, `due_at` to move its
deadline, `assignee` to hand it to someone else. At least one field is required.
## Statuses
| Key | Meaning |
| - | - |
| `open` | not started |
| `in_progress` | being worked on |
| `done` | finished |
| `dismissed` | closed without doing it |
`done` and `dismissed` both close the task and stamp `completed_at`. Setting `open`
or `in_progress` on a closed task reopens it and clears `completed_at`.
This works on any **team-side** task of the case, including the ones Penbox created
itself — so your integration can close a Penbox follow-up once your side has done
the work.
Tasks assigned to a **contact** are read-only through the API: they close when the
contact submits the form Penbox sent them. Updating one returns `403`, and so does
moving a team-side task onto a contact.
There is no delete. Removing a task stays a manual action inside Penbox. Use
`dismissed` for "this no longer needs doing".
The task must belong to the case in the path. A task id from another case returns
a `404`.
```bash Close it theme={null}
curl --request PATCH \
--url https://connect.penbox.io/v1/cases/{id}/tasks/{taskId} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "status": "done" }'
```
```bash Reassign theme={null}
curl --request PATCH \
--url https://connect.penbox.io/v1/cases/{id}/tasks/{taskId} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "assignee": { "email": "karel@firm.example" } }'
```
```bash Unassign theme={null}
curl --request PATCH \
--url https://connect.penbox.io/v1/cases/{id}/tasks/{taskId} \
--header 'Authorization: Bearer ' \
--header 'Content-Type: application/json' \
--data '{ "assignee": null }'
```
```json 200 theme={null}
{
"id": "2c7e2ddd-ece1-4c38-9c2d-47aaaf50be97",
"created_at": "2026-08-31T10:12:04.100Z",
"case": { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6" },
"type": "manual",
"title": "Call the client back",
"description": "The client asked for a callback before Friday.",
"status": "done",
"due_at": "2026-09-04T00:00:00.000Z",
"completed_at": "2026-09-02T14:31:00.000Z",
"step_key": null,
"assignee": {
"kind": "member",
"id": "5f7e2c17-e9be-47c9-8049-0b35d8eb0ada",
"email": "amina@firm.example",
"given_name": "Amina",
"family_name": "Berger"
}
}
```
```json 404 theme={null}
{
"statusCode": 404,
"error": "Not Found",
"message": "Task with ID \"2c7e2ddd-ece1-4c38-9c2d-47aaaf50be97\" not found on this case"
}
```
# List Workspaces
Source: https://docs.penbox.io/api-reference/workspaces/list-workspaces
GET /workspaces
Retrieve all Penbox workspaces that the authenticated user has authorized your application to access.
Retrieve all Penbox workspaces that the authenticated user has authorized your application to access.
## Workspace Slug
The `slug` is a URL-safe, unique identifier for the workspace. It's used in API calls to specify which workspace you want to interact with.
**Slug format:**
* Lowercase letters, numbers, and hyphens
* No spaces or special characters
* Unique across all Penbox workspaces
**Examples:**
* `acme-corp`
* `my-company-123`
* `law-firm-llp`
## Multi-Workspace Support
If a user has authorized your application for multiple workspaces, this endpoint returns all of them. The access token grants access to all authorized workspaces.
# Provision Workspace
Source: https://docs.penbox.io/api-reference/workspaces/provision-workspace
POST /workspaces
Create a fully configured workspace for a client and receive an API token scoped to it
Create a fully configured Penbox workspace for one of your clients, seed it with case templates, register its admins, and receive an API token scoped to that workspace — in a single call. For the end-to-end flow, see [Onboard a Client](/guides/headless-onboard-client).
Workspace provisioning must be enabled on your account — talk to Penbox. A token without it is rejected with a `401`.
**Two silent admins are created.** The `admin` in the payload is your client's. Penbox also adds
your own support account, read from the `support_email` configured once on your partner
workspace — it is never sent here. Without one, the call still succeeds and says so in
`warnings`.
**The token is returned exactly once**, in the `201` response that creates the workspace. Penbox stores nothing but its hash — persist it before you do anything else.
## Idempotency
The call is idempotent on `integration.legalops.source_workspace_id`: one Penbox workspace per workspace on your side. Use the status code to tell a creation from a replay:
| Status | Meaning | `token` |
| - | - | - |
| `201` | The workspace was created | Present, once |
| `200` | The workspace already existed | Absent |
Provisioning never fails halfway: an optional step that did not complete is reported in the `warnings` array, not as an error.
# AI Generated Fields
Source: https://docs.penbox.io/cases/ai-generated-fields
Let AI keep case data up to date for you
AI generated fields are a way for Penbox to keep your case data up to date automatically. Instead of a team member re-reading a form response or a document to fill in a field by hand, the AI does it: it reads everything already on the case and writes a structured value into the field you configured.
AI generated fields live on top of the regular [data schema](/cases/data_schema) — a generated field is a normal field with an AI job attached to it. It can be referenced in steps, automations, and penscript exactly like any other field.
## How it works
For each AI generated field, you configure three things:
* **The field.** Which data point the AI should maintain, and its type (text, number, choices, etc.).
* **The instructions.** A plain-language description of what the field should contain — a summary, a classification, a derived score.
* **The trigger.** The event that makes the AI run: for example, an intake form being completed, a document being uploaded, or a status change.
When the trigger fires, Penbox gathers everything already on the case — forms, contacts, documents, and existing data — and writes a structured value into the field.
**Common uses:**
* Summarize a claim as soon as the intake form is completed.
* Classify a new case into a category and route it to the matching case template.
* Derive a risk level or priority from the answers a client submitted.
## Generated values are always labeled
Every value written by the AI carries an **AI badge** in the case data panel, so anyone working on the case can tell which values were generated and which were entered by a person.
Editing a generated field replaces the AI value with yours. From that point on, the field is treated as manual: the AI will not overwrite it again, even if the trigger fires again.
Use AI generated fields for data you want kept current without babysitting it, and let manual edits take over whenever a human needs to correct or refine the value.
## Configuring AI generated fields
AI generated fields are configured on [case templates](/cases/case_templates), through the [case template assistant](/agent/templates/case-templates). Describe the job in plain language and it sets up the trigger, the target field, and the instructions for you — for example:
> "Summarize the claim into `claim_summary` as soon as the intake form is completed."
> "Classify the case into `case_category` based on the intake form answers, and route it to the matching case template."
You can also set up the same behavior manually from the [case automations builder](/cases/automations#fill-data-with-ai): add an automation with the **Fill data with AI** action, pick the fields to fill, and describe what the AI should write into each.
AI generated fields are configured per case template. Cases created from a template inherit its AI generated fields, the same way they inherit the rest of the data schema.
## Availability
AI generated fields are available today in all workspaces.
***
## Next Steps
Fields, sections, and visibility
Other ways cases update without manual intervention
Reusable blueprints for recurring case types
Configure AI generated fields with Penbox Agent
# Automations
Source: https://docs.penbox.io/cases/automations
The rules that make cases work without manual intervention
Automations are the rules that make cases work without manual intervention. They enable intelligent workflows by automatically executing actions when specific conditions are met — moving cases forward, revealing next steps, and integrating with external systems.
Four automation types power case workflows: status transitions (automatically move from one status to another), step visibility rules (show or hide steps based on case evolution), automatic step execution (trigger steps when conditions are met), and post-automations (send data to external systems via API or n8n when conditions are met).
## Status Transitions
Status transitions automatically move a case from one status to another when conditions are met, eliminating manual status updates and ensuring consistent workflow progression.
**How status transitions work:**
* Monitor case data for changes
* Evaluate conditions when relevant data updates
* Automatically update case status when condition is true
* Log the transition in case timeline
* Can trigger additional automations
**Common status transition patterns:**
**Move to "Ready for Review" when all documents uploaded:**
* Watch: Document upload fields
* Condition: All required documents have values
* Action: Change status to "Ready for Review"
**Move to "Awaiting Payment" when quote approved:**
* Watch: Quote approval field
* Condition: Approval equals "approved"
* Action: Change status to "Awaiting Payment"
**Move to "Closed - Completed" when final signature received:**
* Watch: Signature field
* Condition: Signature is defined
* Action: Change status to "Closed - Completed"
Status transitions eliminate the need for team members to manually update status, ensuring cases progress consistently and nothing falls through the cracks.
[Learn more about Statuses →](/cases/statuses_lifecycle)
## Step Visibility Rules
Step visibility rules hide or reveal steps based on case evolution, showing team members only the actions that are relevant to the current case state.
**How step visibility works:**
* Steps can be hidden by default
* Conditions determine when steps become visible
* Based on case data, status, or other factors
* Steps appear in the case when conditions are met
**Common visibility patterns:**
**Show "Request Additional Documents" only when initial review incomplete:**
* Condition: Review status equals "incomplete"
* Effect: Step appears for case manager to request more documents
**Show "Premium Service Options" only for high-value customers:**
* Condition: Annual revenue greater than threshold
* Effect: Step appears to offer premium services
**Show "Compliance Review" only for regulated industries:**
* Condition: Industry in regulated list
* Effect: Step appears for compliance workflow
**Show "Multi-Signature Workflow" only when multiple signers needed:**
* Condition: Number of signers greater than 1
* Effect: Multi-signature step appears
Step visibility rules keep case workflows clean by showing only relevant actions, reducing confusion and ensuring team members focus on what matters.
## Automatic Step Execution
Automatic step execution triggers a step when specific conditions are met, enabling fully automated workflows that respond to case data changes.
**How automatic execution works:**
* Monitor case data for changes
* Evaluate conditions when data updates
* Automatically execute the step (send email, SMS, form) when condition is true
* Step appears in timeline as executed
* Contact receives communication automatically
**Common automatic execution patterns:**
**Send reminder email if no response after 3 days:**
* Watch: Form sent date, form response status
* Condition: 3 days elapsed and no response received
* Action: Execute "Reminder Email" step
**Send approval request when all documents collected:**
* Watch: Document fields
* Condition: All required documents uploaded
* Action: Execute "Send Approval Form" step
**Send welcome email when case status changes to "Active":**
* Watch: Case status
* Condition: Status equals "Active"
* Action: Execute "Welcome Email" step
**Trigger compliance check when high-risk flag set:**
* Watch: Risk assessment field
* Condition: Risk level equals "high"
* Action: Execute "Compliance Review Form" step
Automatic step execution sends communications to contacts automatically. Test conditions thoroughly to ensure emails, SMS, and forms are sent at the right time.
## Post-Automations
Post-automations send data to external systems via API or n8n when conditions are met, enabling seamless integration between Penbox and back-office systems.
**How post-automations work:**
* Monitor case data for changes
* Evaluate conditions when data updates
* Send HTTP request to external system when condition is true
* Log request and response in timeline
* Can retry on failure
**Common post-automation patterns:**
**Send to CRM when case status changes to "Closed":**
* Watch: Case status
* Condition: Status equals "Closed"
* Action: POST to CRM API with case data
* Payload: Case ID, customer data, outcome
**Trigger n8n workflow when signature collected:**
* Watch: Signature field
* Condition: Signature defined
* Action: POST to n8n webhook
* Payload: Signed document, case details
**Update accounting system when invoice approved:**
* Watch: Invoice approval field
* Condition: Approval equals "approved"
* Action: POST to accounting API
* Payload: Invoice details, amounts, dates
**Send to document management system when case closed:**
* Watch: Case status
* Condition: Status equals "Closed"
* Action: POST documents to DMS
* Payload: All case documents and metadata
Post-automations enable Penbox to act as the intelligent interaction layer while seamlessly integrating with existing back-office systems via API.
## AI-Specific Automations
Penbox Intelligence enables AI-powered automations for email drafting and data extraction.
### Email Drafting Prompts
Configure an email drafting prompt so Intelligence prepares draft responses to incoming emails automatically.
**How it works:**
* Define a prompt that tells Intelligence how to draft responses
* When emails arrive, Intelligence analyzes the content
* Draft response is prepared based on your prompt
* Draft appears in case timeline for review before sending
**Example prompts:**
**Customer support:**
```text theme={null}
Draft a professional response acknowledging the customer's inquiry,
confirming we've received their message, and explaining the next steps
in our process. Maintain a friendly, helpful tone.
```
**Claims processing:**
```text theme={null}
Draft a response confirming we've received the claim, listing what
documents we still need, and providing an estimated timeline for review.
Be empathetic and professional.
```
**Best practices for prompts:**
* Be specific about tone and style
* Define what information should be included
* Specify any compliance requirements
* Mention if certain phrases should be avoided
[Learn more about Intelligence →](/intelligence/introduction)
### Fill Data with AI
The **Fill data with AI** action uses Penbox Intelligence to write values into existing case-data fields when the automation runs. It is the manual, visual counterpart to [AI generated fields](/cases/ai-generated-fields): instead of describing the job to the case template assistant, you configure the action directly in the automations builder.
**How it works:**
* Add an automation and choose the **Fill data with AI** action.
* (Optional) Add **custom instructions** — case-wide context for the AI, such as tone, language, or case type.
* Add one or more **output fields**: pick an existing case-data field and describe, in plain language, what the AI should write into it.
* When the automation's trigger fires (a watched field changes or a condition is met), Penbox reads everything already on the case and writes a structured value into each output field.
**Example — compare two fields:**
* Watch: `data.data_1`, `data.data_2`
* Custom instructions: "Compare the two data fields."
* Output field `match`: "Return true if `data_1` equals `data_2`, otherwise false."
Values written by this action carry the same **AI badge** as other AI generated fields, and a manual edit takes the field over from then on.
Use **Fill data with AI** when you want to configure an AI fill yourself in the automations builder. For a guided setup, describe the job to the [case template assistant](/agent/templates/case-templates) instead.
## Automation Execution
**When automations run:**
* Automations evaluate whenever case data changes
* Watched fields (if specified) trigger evaluation when they change
* Conditions are evaluated against current case data
* Actions execute when condition evaluates to true
**Execution tracking:**
* All automation executions appear in case timeline
* Shows automation name, timestamp, and outcome
* Success or failure status displayed
* Full execution details available on click
* Can retry failed automations
**Performance considerations:**
* Watch specific fields to reduce unnecessary evaluations
* Use execute\_once for one-time transitions
* Test conditions before deploying to production
## Common Patterns
### Sequential Workflow
Combine status transitions and step execution to create sequential workflows:
1. Form completed → Status: "Under Review"
2. Status "Under Review" → Execute "Request Documents" step
3. Documents uploaded → Status: "Ready for Approval"
4. Status "Ready for Approval" → Execute "Send Approval Form" step
5. Approval received → Status: "Closed - Approved"
### Conditional Branching
Use step visibility and execution for different paths:
**If customer type is "Enterprise":**
* Show "Enterprise Onboarding" steps
* Execute "Assign Account Manager" step
* Send to enterprise CRM endpoint
**If customer type is "Standard":**
* Show "Standard Onboarding" steps
* Execute "Send Welcome Email" step
* Send to standard CRM endpoint
### Integration Workflow
Combine multiple automation types for complete integration:
1. Data collected → Post to validation API
2. Validation passes → Status: "Validated"
3. Status "Validated" → Execute "Send Contract" step
4. Contract signed → Post to accounting system
5. Accounting updated → Status: "Closed - Complete"
***
## Next Steps
Configure automations in templates
Understand status transitions
Learn about automatic step execution
Define data for automation conditions
# Case Creation
Source: https://docs.penbox.io/cases/case_creation
The four ways cases come into existence
Cases can be created in four different ways, depending on your workflow. Each method serves different use cases and provides different defaults and configuration options.
## Manually in Penbox
Create a case directly from the Penbox app. This is the most common method for internal processes.
**What's required:**
* At least one main contact (can be added during creation or immediately after)
* Optionally select a case template
**What happens:**
* Case appears immediately in your case list
* If a template is selected, the case inherits the template's structure (fields, sections, contacts, steps, automations)
* If no template is selected, case starts blank and you build the structure as needed
* Main contact can be selected from existing contacts or created as new
* Case owner defaults to the creating user but can be changed
**Defaults applied:**
* Status: Draft
* Owner: Creating user
* Waiting For: None
* Reference: Auto-generated 6-character code
**Best for:**
* Customer onboarding
* Project kickoffs
* Internal workflows
* Processes that require manual oversight
Cases don't require a template. You can create a blank case and add fields, sections, and contacts as you go. For recurring workflows, templates save time by pre-configuring the structure.
## From an Incoming Email
Automatically create a case when an email arrives in a connected mailbox. Works with Email Intelligence or rule-based email routing.
### With Email Intelligence
When Email Intelligence labels an incoming email with the "In Penbox" trigger, a case is automatically created with the email content and sender as context.
**What's required:**
* Connected mailbox (Gmail or Outlook)
* Email Intelligence enabled
* "In Penbox" label configured
**What happens:**
* Email sender automatically becomes the main contact
* Email body and subject are captured in the case
* Any attachments are added to the case
* Case title can be auto-generated by AI based on email content
* Email appears in the case timeline
**Defaults applied:**
* Status: Pending
* Owner: Assigned based on workspace rules or unassigned
* Waiting For: Owner
* Reference: Auto-generated
### With Rule-Based Routing
Without Intelligence, cases can be created from emails sent to a dedicated mailbox (e.g., [support@yourcompany.com](mailto:support@yourcompany.com)).
**What's required:**
* Dedicated mailbox configured
* Email routing rule set up
**What happens:**
* Every email to the address creates a case
* Email sender becomes the main contact
* Email content captured in timeline
**Best for:**
* Support tickets
* Customer inquiries
* Claims submissions via email
* High-volume intake workflows
## From a Completed Form
When a form linked to case creation is completed, a case is automatically generated with the collected data.
**What's required:**
* Form template configured with "Create case on completion" setting
* Form must be sent and completed by a contact
**What happens:**
* Case is created immediately when form is marked complete
* Form respondent automatically becomes the main contact
* All form data is mapped to case fields (based on configuration)
* Form appears in the case timeline as the first event
* If configured, automations can trigger based on form data
**Defaults applied:**
* Status: Configurable (typically Pending or In Progress)
* Owner: Assigned based on form configuration or workspace rules
* Waiting For: Configurable
* Reference: Auto-generated
**Configuration options:**
* Which case template to use (or blank case)
* Default status for new cases
* Default owner assignment
* Field mapping (form elements to case fields)
* Initial automations to trigger
**Best for:**
* Customer intake forms
* Claims reporting
* Support request forms
* Application submissions
* Lead generation forms
Form-to-case creation is one of the most powerful features in Penbox, enabling external contacts to initiate cases directly through public forms.
## Via API
Create cases programmatically from external systems, enabling Penbox to serve as the intelligent interaction layer for back-office workflows.
**What's required:**
* API key with case creation permissions
* Minimum: main contact information (email or phone)
* Optionally: case template reference, initial data, owner assignment
**What happens:**
* Case is created via HTTP POST request to `/cases` endpoint
* Full control over all case properties
* Can pre-populate any case data
* Can assign owner, status, and contacts programmatically
* Returns case reference and ID for tracking
**Request parameters:**
* `template_id` - Optional case template to use
* `main_contact` - Contact information (email, phone, name)
* `data` - Initial case data (key-value pairs)
* `status` - Initial status (defaults to Draft)
* `owner_id` - Assign to specific team member
* `secondary_contacts` - Additional contacts with roles
**Best for:**
* Integration with CRM systems
* Automated case creation from webhooks
* Bulk case imports
* Connecting legacy systems to Penbox
* Building custom intake portals
**Example use cases:**
* E-commerce platform creates case when order requires follow-up
* CRM system creates case when lead reaches certain stage
* Payment processor creates case when dispute is filed
* HR system creates case for onboarding new employees
API creation enables Penbox to act as the intelligent interaction layer between your contacts and back-office systems, handling all communication while integrating seamlessly with existing tools.
[View API Documentation →](/api-reference/cases/create-case)
***
## Next Steps
Learn about cases and their structure
Design reusable templates
Understand workflow steps
API documentation
# Case Templates
Source: https://docs.penbox.io/cases/case_templates
Reusable blueprints for recurring case types
A case template is a reusable blueprint for recurring case types. Templates pre-configure the steps, contacts, data schema, automations, and statuses that a new case will start with. When you create a case from a template, the entire structure is applied instantly.
A case does not require a template — templates exist to save time on repeating workflows. You can create a blank case and build it as you go, or use templates to ensure consistency across similar cases.
Templates are the design-time artifact (the reusable pattern), while cases are the runtime instances (the actual work items).
## What Templates Define
Templates pre-configure every aspect of a case:
**Data schema:**
* Fields and their types (text, number, date, file, etc.)
* Sections organizing related fields
* Visibility settings for each field
* AI descriptions for Intelligence extraction
* Default values
**Contacts:**
* Main contact requirements
* Secondary contact roles (broker, expert, partner, etc.)
* Contact keys for referencing in steps
**Steps:**
* Predefined emails, SMS, and forms
* Step sequence and dependencies
* Conditional visibility rules
* Templates for email content
**Statuses:**
* Custom statuses for your workflow
* Status hierarchy and parent statuses
* Automatic transition conditions
**Automations:**
* Status transitions based on conditions
* Step visibility rules
* Automatic step execution
* Post-automations to external systems
When you create a case from a template, all this structure is applied instantly. Individual cases can then be customized without affecting the template.
## Creating Case Templates
Case templates are created using the same interface as regular cases. Templates are themselves cases with a special flag (`is_template: true`), which means:
* You use the same interface to design templates as you do to work with cases
* Templates can be tested like real cases
* What you see when designing is what users see when working
**Basic template information:**
* **Title** - Name shown when selecting template
* **Description** - What this template is for and when to use it
* **Locale** - Default language for the case
For compliance-heavy workflows, use the description to document regulatory requirements, guiding team members on what the case type must meet.
## Naming and Managing Templates
**Creating templates:**
1. Create a new case and mark it as template
2. Design the data schema, contacts, steps, and automations
3. Test the template by creating test cases
4. Publish for team use
**Duplicating templates:**
* Clone existing templates to create variations
* Useful for similar workflows with minor differences
* Maintains original template while creating new version
**Managing templates:**
* Templates can be updated at any time
* Changes don't affect existing cases created from the template
* Future cases use the updated template structure
Changes to templates only affect new cases created after the change. Existing cases maintain their original structure.
## Template Structure Examples
### Customer Onboarding Template
**Data schema sections:**
* Company Information (name, registration, industry)
* Contact Details (email, phone, address)
* Financial Data (revenue, IBAN, tax ID)
* Documents (registration, ID, proof of address)
**Contacts:**
* Main Contact (customer representative)
* Billing Contact (for invoicing)
**Steps:**
1. Welcome Email - Introduce process
2. KYC Form - Collect customer information
3. Document Request - Request required documents
4. Compliance Review - Internal approval form
5. Welcome Package - Send final confirmation
**Statuses:**
* Pending → In Progress → Compliance Review → Approved → Closed
**Automations:**
* Move to "Compliance Review" when all documents uploaded
* Send reminder if no response after 3 days
* Post to CRM when status changes to "Closed"
### Claims Processing Template
**Data schema sections:**
* Claim Information (date, type, description)
* Claimant Details (name, policy number, contact)
* Incident Details (location, witnesses, police report)
* Financial (claim amount, deductible)
* Documents (photos, reports, receipts)
**Contacts:**
* Main Contact (claimant)
* Witness (optional secondary contact)
**Steps:**
1. Claim Acknowledgment - Confirm receipt
2. Claim Declaration Form - Detailed claim info
3. Supporting Documents Form - Upload evidence
4. Assessment Request - Internal review
5. Decision Email - Communicate outcome
**Statuses:**
* Submitted → Under Review → Investigation → Decision → Closed
**Automations:**
* Move to "Investigation" when claim amount > threshold
* Assign to specialist when claim type is complex
* Send to payment system when approved
## Templates and Individual Cases
**When creating a case from a template:**
* Case inherits all template structure
* Data schema, contacts, steps, statuses copied
* Case can be customized independently
* Changes to case don't affect template
**Customizing individual cases:**
* Add fields specific to this case
* Modify sections for unique needs
* Add or remove contacts as required
* Adjust steps for special circumstances
This approach combines the efficiency of templates with the flexibility to handle exceptions and special situations.
Use templates for standardized processes to ensure consistency. Use individual modifications for unique situations that need customization.
## Best Practices
**Design for your actual process:**
* Map your workflow before creating the template
* Define statuses that match real stages
* Include all data you need to collect
* Plan for edge cases and exceptions
**Keep templates maintainable:**
* Use clear, descriptive names for fields and steps
* Document why each field exists
* Add help text for complex fields
* Use AI descriptions for Intelligence features
**Test before deploying:**
* Create test cases from the template
* Execute all steps to verify they work
* Test automations with sample data
* Validate forms and emails render correctly
**Plan for compliance:**
* Document regulatory requirements in description
* Include audit trail requirements
* Define mandatory fields for compliance
* Set appropriate visibility settings
**Optimize for users:**
* Group related fields in sections
* Use "hidden if empty" for optional fields
* Provide clear step instructions
* Set helpful default values
***
## Next Steps
Learn about cases
Design case data structures
Configure workflow steps
Set up automations
# Contacts
Source: https://docs.penbox.io/cases/contacts
The people involved in a case
Contacts are the people involved in a case. Every case has a **Main Contact** — the primary individual the case is about. Cases can also have **Secondary Contacts** — additional stakeholders like brokers, experts, partners, or opposing parties.
Each contact is identified with a unique key when defining the case, which allows the system to reference them in steps, notifications, and data throughout the platform.
## Main Contact
The main contact is the primary individual a case is about and the primary recipient for all external communications.
**Every case requires a main contact.** The main contact:
* Receives forms sent from the case
* Receives email communications and notifications
* Is the primary point of contact for the matter
* Can be an individual person or organization representative
* Must have at least an email address or phone number to receive communications
A case without a main contact cannot send forms or communications to external parties. Always add a main contact to enable full case functionality.
**Main contact information:**
* **Name** - Full name of the individual
* **Email** - Primary email address (required for email communications)
* **Phone** - Phone number (required for SMS communications)
* **Language** - Preferred communication language
* **Organization** - Optional company or organization name
* **Additional fields** - Custom fields based on your workspace configuration
When a case is created from a form submission or incoming email, the sender's information is automatically added as the main contact.
## Secondary Contacts
Secondary contacts are additional stakeholders involved in the case beyond the main contact. These can include:
**Common secondary contact roles:**
* **Brokers** - Insurance brokers, real estate agents, financial advisors
* **Experts** - Technical experts, appraisers, medical professionals
* **Partners** - Business partners, co-applicants, joint account holders
* **Legal representatives** - Lawyers, guardians, trustees
* **Billing contacts** - Separate contact for invoicing and payments
* **Technical contacts** - IT contacts, system administrators
* **Opposing parties** - In legal or dispute cases
Each secondary contact can have a specific role that defines their involvement in the case.
**Secondary contact configuration:**
* **Role** - Defines the contact's relationship to the case
* **Key** - Unique identifier for referencing in steps and automations
* **Contact information** - Same fields as main contact
* **Permissions** - What they can see or do (if applicable)
Use secondary contacts to involve multiple stakeholders in a case while maintaining clear roles and responsibilities.
## Contact Keys
Each contact (main and secondary) is identified with a unique key when defining the case. This key system allows the platform to reference contacts in various contexts:
**Where contact keys are used:**
* **Steps** - Reference which contact receives an email or form
* **Notifications** - Specify who gets notified about case events
* **Data fields** - Reference contact information in case data
* **Email templates** - Insert contact details dynamically
* **Automations** - Trigger actions based on contact responses
**Key naming:**
* Main contact typically uses the key `main`
* Secondary contacts use descriptive keys like `broker`, `expert`, `partner`, `billing`
* Keys must be unique within a case
* Keys are defined in case templates or when adding contacts to individual cases
Contact keys are explained conceptually here. For the technical notation and variable syntax, see the penscript documentation.
## Assigning Contacts
Contacts can be assigned to a case in several ways:
### Select Existing Contact
Choose from contacts already in your workspace:
* Search by name, email, or organization
* View contact history and previous cases
* Ensures data consistency across cases
* Maintains single source of truth for contact information
### Create New Contact
Add a new contact directly when creating or updating a case:
* Enter contact information manually
* Contact is added to workspace contact database
* Available for future cases
* Can be updated centrally
### Automatic Assignment
Contacts are automatically assigned in certain scenarios:
* **From form submission** - Form respondent becomes main contact
* **From incoming email** - Email sender becomes main contact
* **Via API** - Contact information provided in API request
### Bulk Import
For campaigns or large case imports:
* Upload CSV file with contact information
* Map columns to contact fields
* Create multiple cases with pre-assigned contacts
* Useful for annual updates or mass communications
## Language Preferences
Each contact can have a preferred communication language. This affects:
**Form language:**
* Forms sent to the contact appear in their preferred language
* Form templates must have translations available
* Automatic language selection based on contact preference
**Email language:**
* Email templates can have multiple language versions
* System selects appropriate version based on contact language
* Ensures professional communication in contact's language
**Supported languages depend on your workspace configuration.**
Set contact language preferences early to ensure all communications are sent in the correct language from the start.
## How Contact Information Flows
Contact information flows through the platform in several ways:
### In Steps
When sending forms or emails:
* Select which contact receives the communication
* Contact's email or phone is automatically used
* No need to re-enter contact information
* Changes to contact details automatically reflect in future communications
### In Data Fields
Case data can reference contact information:
* Pull contact name into document templates
* Use contact email for automated notifications
* Reference contact organization in reports
* Maintain consistency across all case materials
### In Notifications
Automations can notify specific contacts:
* Send confirmation emails to secondary contacts
* Notify brokers when status changes
* Alert partners when documents are signed
* Configure per contact role
### In Reports
Contact information appears in case reports:
* Filter cases by contact
* View all cases for a specific contact
* Track contact interactions across cases
* Analyze contact engagement
## Managing Contacts
**Updating contact information:**
* Changes to workspace contacts affect all future communications
* Existing case timeline preserves original contact info (audit trail)
* Update in one place, applies to new interactions
**Removing contacts:**
* Secondary contacts can be removed from cases
* Main contact cannot be removed (case requirement)
* Removed contacts remain in timeline history
**Contact privacy:**
* Contact information is stored securely
* Access controlled by workspace permissions
* Complies with data protection requirements
* Can be anonymized or deleted per retention policies
***
## Next Steps
Learn about cases and main contacts
Send communications to contacts
Reference contact information in data
Define contact roles in templates
# Data Schema Overview
Source: https://docs.penbox.io/cases/data_schema
The structured data a case collects
A data schema defines what information the case tracks — organized into sections containing individual data points. Each data point has a type (Text, Number, Currency, Date, File, Multiple Choice, etc.) and visibility settings.
Cases created from templates inherit the template's data schema. Cases created from scratch start with a blank schema. You can modify a case's data schema at any time by adding, modifying, or removing fields and sections. Structure changes apply only to that individual case and do not affect the template.
Each data point has a unique key that can be referenced throughout the
platform. Keys are explained conceptually here — for the technical notation
and variable syntax, see the penscript documentation.
## Field Types
Cases support 10 field types, each with specific configuration options and use cases. Click any field type below to see detailed configuration, examples, and best practices.
Single or multi-line text input for names, descriptions, and IDs
Numeric values for quantities, scores, and measurements
Monetary values with currency formatting for financial amounts
Date picker for birth dates, deadlines, and timestamps
Single yes/no option for agreements and consent flags
Multiple selections for services, features, and categories
Single selection for country, priority, and status
File upload for documents, images, and contracts
Digital signatures with authentication for legally binding agreements
External API data fetch for credit checks and verification
## Field Properties
Each field in a case has several properties:
| Property | Description | Example |
| - | - | - |
| **Key** | Unique identifier | `company_name`, `birth_date` |
| **Type** | Field data type | `text`, `number`, `date` |
| **Name** | Display label | "Company Name", "Date of Birth" |
| **Description** | Help text | "Legal name of your company" |
| **Value** | Current data | "Acme Corp", "1990-01-15" |
| **Visibility** | Display setting | Always, hide when empty, always hide |
| **Section** | Grouping | "Company Information" section |
## Visibility Settings
Fields can have different visibility settings to control when they appear:
**Always visible** - Field always appears in the case view, whether it has a value or not. Use for essential fields, required information, and key data points.
**Hidden if empty** - Field only shows when it has a value. Empty fields are hidden. Use for optional fields, conditional data, and supplementary information. This keeps case views clean and focused on relevant information.
**Always hidden** - Field is hidden from view but data is still stored. Use for technical fields, IDs, system data, and calculated values used in automations.
Use "hidden if empty" for optional fields to keep the case view uncluttered
while maintaining complete data storage for compliance.
## Sections
Sections organize related fields together, making cases easier to navigate and understand. Sections are groups of fields with a common purpose or theme.
**Example case structure:**
```
📁 Company Information
- Company Name (text)
- Registration Number (text)
- Industry (choices)
- Website (text)
📁 Financial Data
- Annual Revenue (number)
- IBAN (text)
- Tax ID (text)
📁 Documents
- Company Registration (file)
- ID Document (file)
- Proof of Address (file)
```
**Benefits of sections:**
* **Organization** - Group related fields logically
* **Navigation** - Easier to find specific information
* **Clarity** - Clear separation of different data types
* **Presentation** - Better visual organization
* **Workflow** - Align sections with process steps
Sections are defined in the case template. You cannot add or remove sections
from existing cases, but you can add or remove fields within sections.
## Data Updates
Case data can be updated through multiple channels:
### Manual Updates
Team members can update case fields directly:
* Edit fields in the case view
* Change values as needed
* Updates are tracked in timeline
* Changes can trigger automations
### Form Responses
When contacts submit forms, data flows into the case:
* Form fields map to case fields (based on configuration)
* Responses automatically update case data
* Files are attached to case
* Timeline shows form submission
### Automations
Automated workflows can update fields:
* Calculated values based on other fields
* Status updates based on conditions
* Data transformations
* External API responses
### API Call Fields
API Call fields fetch data from external sources:
* Credit checks from financial services
* Company data from business registries
* Verification services
* Real-time data lookups
## Data Validation
Field values are validated based on their type:
| Field Type | Validation |
| - | - |
| **Text** | Length limits, pattern matching |
| **Number** | Numeric values, min/max ranges |
| **Date** | Valid dates, date ranges |
| **Checkbox** | Boolean (true/false) |
| **Choices** | Must be from defined options |
| **File** | File type, size limits |
Validation rules are defined in the case template and cannot be changed for
individual cases.
## Keys and References
Each data point has a unique key that identifies it throughout the platform. These keys allow you to:
* Reference data in steps and automations
* Map form responses to case fields
* Use data in email and document templates
* Create calculated fields based on other data
* Trigger automations based on data changes
Keys are typically lowercase with underscores (e.g., `company_name`, `annual_revenue`, `birth_date`) and must be unique within the case template.
Keys are described conceptually here. For the full technical syntax including
dot notation and variable references, see the penscript documentation.
***
## Next Steps
Design data schemas in templates
Learn about case contacts
Automate based on data changes
Collect data through forms
# API Call Field
Source: https://docs.penbox.io/cases/data_schema/api_call
Call an external API from a case and write the response into case fields
The API Call field (`type: "http"`) calls an external API and stores the response on the case. With the optional `mapping`, parts of the response are written into other case fields — credit scores, company data verification, address lookups, vehicle registrations, or any external data source with an HTTPS API.
A team member runs the call from the case view (the **Run** button on the field); the response and its status are stored on the field itself, and mapped values land in the target fields immediately.
## When to Use
**Use API Call fields for:**
* Credit score checks
* Company data verification (registration, VAT validation)
* Address validation and geocoding
* Bank account verification (IBAN validation)
* Vehicle registration lookups
* API-based enrichment services
**Consider alternatives:**
* Use **Text**, **Number**, or other fields for manual data entry
* Use **case automations** (`http` action) to call an API automatically when case data changes — the API Call field itself only runs on demand
* Use webhooks for pushing data to external systems
## Configuration
A field entry in the case schema:
| Option | Description | Type | Default |
| - | - | - | - |
| `key` | Unique identifier | String | Required |
| `name` | Display label | String | Required |
| `description` | Help text | String | Optional |
| `type` | Must be `"http"` | String | Required |
| `options` | The API call configuration (below) | Object | Required to run |
| `visibility` | `always-visible`, `hide-when-empty`, `always-hide` | String | `always-visible` |
| `section` | Section this field belongs to | String | Optional |
### `options`
| Property | Description | Type |
| - | - | - |
| `url` | HTTPS endpoint (required to run). Supports expressions | String |
| `method` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE` | String (default `GET`) |
| `headers` | Request headers | Object |
| `body` | Request body, for `POST`/`PUT`/`PATCH` | Any |
| `params` | Query-string parameters | Object |
| `bearer` | Bearer token (sets the `Authorization` header) | String |
| `basic` | Basic-auth credentials | String |
| `accept` | `Accept` header override | String |
| `ct` | `Content-Type` header override | String |
| `responseMode` | `full_ok` (default): a non-2xx marks the field in error. `full`: any status is stored as a normal response | String |
| `payloadType` | How the response body is read: `parsed` (default), `text`, `json`, `data-uri`, `blob` | String |
| `mapping` | Writes response values into other case fields (below) | Object |
| `loadingText` | Label shown while the call runs | String |
Every option value may contain expressions resolved against the case at run time: `{data.vat_number}`, `{contacts.main.email}`, and company secrets via `{$secrets.my_api_key}` (never hardcode credentials).
**Unknown option keys are rejected.** Adding an option Penbox does not know — `response_mapping`, `api_config`, `trigger` — fails with a validation error naming the key and pointing at it. The check fires only on keys you *add*: a configuration that already carries a legacy key stays savable, so an unrelated edit never bricks an existing case. A few legacy aliases (`uri`, `json`, `responseType`) are also still accepted for configurations that use them.
An earlier version of this page documented `api_config`, `trigger`, `retry`, `cache_duration`, `auth` and `response_mapping`. Those options never existed and are now rejected at save time. Use the flat `options` object above, and `mapping` instead of `response_mapping`.
## Response Mapping
Without a `mapping`, the response is only stored on the API Call field itself. With one, parts of the response are also written into other case fields — visible in the case immediately, recorded in the case history, and able to trigger automations like any other field update.
Inside mapping values, the parsed response body is available as `@payload`:
```json theme={null}
{
"key": "vat_check",
"type": "http",
"name": "VAT validation",
"options": {
"url": "https://ec.europa.eu/taxation_customs/vies/rest-api/check-vat-number",
"method": "POST",
"body": { "vatNumber": "{data.vat_number}", "countryCode": "{data.country}" },
"mapping": {
"data": {
"company_name": "{@payload.traderName}",
"company_address": { "value": "{@payload.traderAddress}", "replace": false }
}
}
}
}
```
Rules:
* **Targets must be existing field keys** in the case schema. The mapping writes values; it never creates fields. Unwritable targets are skipped (`mapping_skipped`), while valid targets are still written.
* **Two equivalent shapes**: nested `{ "data": { "field": … } }` or flat `{ "data.field": … }`.
* **Values are expressions** — they can read `@payload` but also any case data (`{data.other_field}`).
* **`{ "value": …, "replace": false }`** skips the write when the target already holds a non-empty value. This is the same vocabulary as the form API element's `mapping`.
* A mapping value pointing at a missing response path writes nothing and is reported as unresolved (`mapping_unresolved`) — a mistyped `@payload` path is the most common cause of "the API call worked but the case stayed empty".
After a run, the field itself says how many entries were ignored, whatever the cause. The dot stays green when the call succeeded and at least one entry was written, and turns amber when nothing was written at all. Click the field to see which entries and why.
### Mapping error responses
APIs often return useful bodies on failure (e.g. validation errors with a `400`). The response body is stored even when the call fails, so `@payload` works on the error path too. If you want non-2xx statuses treated as normal responses instead of errors, set `"responseMode": "full"`.
### Per-field error messages
When an API rejects one of the values you sent, it answers with a message about that value. The `errors` root puts that message under the field it is about, instead of leaving it inside the stored response:
```json theme={null}
"mapping": {
"data": { "company_name": "{@payload.traderName}" },
"errors": { "chassisnummer": "{@payload.errors.vin}" }
}
```
The `chassisnummer` row then shows the message in red underneath it, and the field's status reads `1 rejected`.
Rules:
* **Same target rules as `data`**: the key must already exist in the case schema, and it cannot be the API Call field's own key. Bad targets are reported in `mapping_skipped` under the name you wrote (`errors.not_a_field`).
* **`errors` must be an object** to mean the error root. `"errors": "{@payload.x}"` still means "write into a case field literally keyed `errors`" — existing configurations are unaffected.
* **Values are expressions**, and each must resolve to a string or to a list of strings (joined with `, `).
* **A path that resolves to nothing produces no message.** This is the normal case: the same mapping serves the run where the API rejects the field and the run where it accepts it. Unlike a value entry, an unresolved error entry is *not* counted as unresolved and does not affect the field's status.
* **No `{ "value": …, "replace": false }` wrapper** here — an error message is always the last word on the field.
Error messages are **not case data**. They are stored on the API Call field under `field_errors`, so they never appear in the case history, never trigger automations, and are never served by the public API as if they were data about the dossier. The next run replaces them wholesale, and a run that rejects nothing clears them. A message also disappears as soon as a team member edits the row it sits under, because it describes the value that was just replaced.
## Stored value
After a run, the field holds:
```json theme={null}
{
"config": { "…": "the options used for this run" },
"response": { "…": "the parsed response body" },
"status_code": 200,
"status_message": "OK",
"error": null,
"executed_at": "2026-08-13T15:00:00.000Z",
"mapping_written": ["company_name"],
"mapping_skipped": ["data.not_a_field"],
"mapping_unresolved": ["data.company_address"],
"field_errors": { "chassisnummer": "VIN checksum invalid" }
}
```
`error` carries the HTTP error name on failure. The mapping keys only appear when there is something to report: `mapping_written` names the fields that were filled, `mapping_skipped` the targets that do not exist, `mapping_unresolved` the entries whose value expression resolved to nothing, and `mapping_error` a mapping that could not be evaluated at all. Click the field's status in the case view to inspect this payload.
`field_errors` is the odd one out: it is not a diagnostic about your configuration but the messages the API returned about the case's data, written by the [`errors` mapping root](#per-field-error-messages). It is keyed by field, and it is the only place those messages exist.
## Editing in the app
In the case view, the field's menu offers **Edit API Call**: method, URL, headers, body, and the response mapping as JSON. Options managed outside the modal (e.g. `responseMode`, `params`) are preserved when saving.
## Examples
### Vehicle registration push
Send case data to a registration service and capture returned errors on the case:
```json theme={null}
{
"key": "register_vehicle",
"type": "http",
"name": "Register vehicle",
"options": {
"url": "https://api.example-insurer.com/RegisterVehicle",
"method": "POST",
"headers": { "X-API-Key": "{$secrets.insurer_api_key}" },
"body": { "vehicle_vin": "{data.vehicle_vin}" },
"mapping": { "data": { "registration_errors": "{@payload.errors}" } }
}
}
```
### Company data enrichment
```json theme={null}
{
"key": "company_data",
"type": "http",
"name": "Company data",
"options": {
"url": "https://api.companyreg.com/v2/companies/{data.company_id}",
"method": "GET",
"bearer": "{$secrets.company_api_token}",
"mapping": {
"data": {
"legal_name": "{@payload.name}",
"registration_date": "{@payload.incorporationDate}"
}
}
}
}
```
## Best Practices
* **Declare the target fields first** — the mapping only writes into fields that already exist in the case schema.
* **Use `{$secrets.…}` for credentials** — never paste API keys into the configuration.
* **Use `replace: false`** for fields a team member may have filled manually and that the API should not overwrite.
* **Read the ignored count on the field** when a target stays empty — it names the entries that could not be written and why, so a mistyped `@payload` path is visible instead of silent.
# Checkbox Field
Source: https://docs.penbox.io/cases/data_schema/checkbox
Single yes/no option for agreements, consent, and binary choices
The Checkbox field captures a single yes/no or true/false value. Checkboxes are ideal for agreements, consent flags, feature toggles, and any binary decision where the user confirms or denies something.
Use Checkbox fields when you need a simple on/off value. Unlike Multiple Choice fields (which allow selecting multiple options from a list), Checkbox fields represent a single boolean decision.
## When to Use
**Use Checkbox fields for:**
* Terms and conditions agreements
* Consent flags (marketing, data processing)
* Feature toggles (enable/disable)
* Confirmation checkboxes ("I confirm this information is accurate")
* Binary status indicators (active/inactive, enabled/disabled)
* Opt-in/opt-out preferences
* Acknowledgments
**Consider alternatives:**
* Use **Choices** for yes/no/maybe or multiple options
* Use **Multiple Choice** for selecting multiple items from a list
* Use **Text** or **Number** for non-binary data
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `terms_agreed` |
| `name` | Display label | String | Required | "I agree to terms and conditions" |
| `description` | Help text for users | String | Optional | "Required to proceed" |
| `required` | Must be checked to proceed | Boolean | `false` | `true` |
| `default` | Default checked state | Boolean | `false` | `false` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Terms Agreement
Require agreement to terms:
```json theme={null}
{
"key": "terms_agreed",
"type": "checkbox",
"name": "I agree to the terms and conditions",
"required": true,
"description": "You must agree to proceed"
}
```
### Marketing Consent
Opt-in for marketing communications:
```json theme={null}
{
"key": "marketing_consent",
"type": "checkbox",
"name": "I agree to receive marketing communications",
"default": false,
"description": "Optional - we respect your privacy"
}
```
### Data Accuracy Confirmation
Confirm data is correct:
```json theme={null}
{
"key": "data_confirmed",
"type": "checkbox",
"name": "I confirm the information provided is accurate and complete",
"required": true
}
```
### Feature Toggle
Enable optional feature:
```json theme={null}
{
"key": "enable_premium_features",
"type": "checkbox",
"name": "Enable premium features",
"default": false,
"description": "Additional features available with premium plan",
"visibility": "hide-when-empty"
}
```
## Validation
Checkbox fields have simple validation:
### Required Validation
* If `required: true`, checkbox must be checked to proceed
* Useful for mandatory agreements (terms, privacy policy)
* Prevents form submission when unchecked
### Boolean Value
* Checked = `true`
* Unchecked = `false`
* Stored as boolean in case data
Checkboxes that are not required can remain unchecked, storing `false` as the
value.
## Checkbox in Automations
Checkbox fields enable powerful conditional logic:
**Status transitions:**
* Move to "Ready for Approval" when all confirmations checked
* Trigger compliance review when specific consent given
* Prevent progress until required checkboxes checked
**Conditional steps:**
* Show additional form when premium features enabled
* Send different email based on consent preferences
* Trigger workflows based on toggle state
**Notifications:**
* Alert team when critical checkbox checked
* Send confirmation when consent given
* Notify compliance when data processing agreed
**Example automation:**
```json theme={null}
{
"watch": ["data.terms_agreed", "data.data_processing_consent"],
"condition": {
":and": ["{data.terms_agreed}", "{data.data_processing_consent}"]
},
"action": {
"type": "status_transition",
"status": "ready_to_proceed"
}
}
```
[Learn more about Automations →](/cases/automations)
## Best Practices
**Use clear, affirmative language:**
* "I agree to receive..." (clear what checking means)
* Avoid double negatives ("I don't want to not receive...")
* State the action being confirmed
**Don't overuse checkboxes:**
* Too many checkboxes overwhelm users
* Group related agreements when possible
* Consider single confirmation with linked terms
**Mark required appropriately:**
* Only mark required if absolutely necessary
* Explain why it's required in description
* Legal requirements should be clearly stated
**Default unchecked for consent:**
* Marketing consent should default to `false`
* Optional features should default to `false`
* Never pre-check consent checkboxes (regulatory compliance)
**Use for regulatory compliance:**
* GDPR consent requires explicit checkbox
* Terms agreements need clear checkbox
* Maintain audit trail of when checkbox was checked
**Visibility settings:**
* Keep consent checkboxes always visible
* Use "hide when empty" for optional toggles
* Never hide required checkboxes
***
## Related Field Types
Use for selecting multiple items from a list
Use for yes/no/maybe or multiple options
Use for free-form agreement text or detailed consent
Back to Data Schema overview
# Choices Field
Source: https://docs.penbox.io/cases/data_schema/choices
Single selection from predefined options
The Choices field allows users to select a single option from a predefined list. This field type displays as radio buttons or a dropdown menu, ensuring users choose exactly one option from the available choices.
Use Choices fields when you need to collect a single selection from a fixed set of possibilities — country, priority, status, category, or any scenario where only one option should be selected.
## When to Use
**Use Choices fields for:**
* Country or region selection
* Priority levels (low, medium, high)
* Categories or types
* Status indicators
* Gender or salutation
* Yes/No questions with additional options
* Industry or sector
* Payment methods
* Communication preferences
* Urgency levels
**Consider alternatives:**
* Use **Multiple Choice** for selecting multiple items
* Use **Checkbox** for simple yes/no only
* Use **Text** for free-form input
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `country` |
| `name` | Display label | String | Required | "Country" |
| `description` | Help text for users | String | Optional | "Select your country" |
| `options` | Array of available options | Array | Required | See below |
| `required` | Must select an option | Boolean | `false` | `true` |
| `default` | Default selected value | String | None | `belgium` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
### Options Configuration
Each option in the `options` array:
| Property | Description | Type | Required | Example |
| - | - | - | - | - |
| `value` | Internal value stored | String | Yes | `belgium` |
| `label` | Display text shown to user | String | Yes | "Belgium" |
## Examples
### Country Selection
Select a single country:
```json theme={null}
{
"key": "country",
"type": "choices",
"name": "Country",
"description": "Select your country of residence",
"required": true,
"options": [
{ "value": "belgium", "label": "Belgium" },
{ "value": "france", "label": "France" },
{ "value": "germany", "label": "Germany" },
{ "value": "netherlands", "label": "Netherlands" },
{ "value": "luxembourg", "label": "Luxembourg" },
{ "value": "other", "label": "Other" }
]
}
```
### Priority Level
Set case priority:
```json theme={null}
{
"key": "priority",
"type": "choices",
"name": "Priority",
"description": "How urgent is this request?",
"default": "medium",
"options": [
{ "value": "low", "label": "Low - Routine" },
{ "value": "medium", "label": "Medium - Normal" },
{ "value": "high", "label": "High - Urgent" },
{ "value": "critical", "label": "Critical - Immediate" }
]
}
```
### Industry Sector
Select industry:
```json theme={null}
{
"key": "industry",
"type": "choices",
"name": "Industry Sector",
"description": "Primary business sector",
"required": true,
"options": [
{ "value": "finance", "label": "Financial Services" },
{ "value": "healthcare", "label": "Healthcare" },
{ "value": "technology", "label": "Technology" },
{ "value": "manufacturing", "label": "Manufacturing" },
{ "value": "retail", "label": "Retail" },
{ "value": "real_estate", "label": "Real Estate" },
{ "value": "other", "label": "Other" }
]
}
```
### Yes/No/Maybe
Three-option choice:
```json theme={null}
{
"key": "renewal_intention",
"type": "choices",
"name": "Do you intend to renew?",
"description": "Let us know your renewal plans",
"options": [
{ "value": "yes", "label": "Yes, I will renew" },
{ "value": "no", "label": "No, I will not renew" },
{ "value": "undecided", "label": "Not sure yet" }
]
}
```
## Validation
Choices fields enforce single-selection validation:
### Required Validation
* If `required: true`, user must select an option
* Empty selection not allowed
* Useful for essential categorization
### Option Validation
* User can only select from predefined options
* No free-form input
* Ensures data consistency and enables automation logic
## Stored Values
Choices fields store a single string value:
**Example stored data:**
```json theme={null}
{
"country": "belgium"
}
```
**Empty state:**
```json theme={null}
{
"country": null
}
```
The stored value is the `value` from the selected option, not the `label`.
## Choices in Automations
Choices fields enable powerful conditional workflows:
**Check for specific value:**
```json theme={null}
{
"condition": {":eq": "{data.country}", "belgium"}
}
```
**Check if value in set:**
```json theme={null}
{
"condition": {":in": "{data.priority}", ["high", "critical"]}
}
```
**Route based on selection:**
* Assign to country-specific team based on country
* Escalate high-priority cases automatically
* Show industry-specific steps based on industry
* Apply different pricing based on category
**Status transitions:**
* Move to "Urgent Queue" when priority is high
* Route to specialist when specific industry selected
* Apply different SLAs based on priority
[Learn more about Automations →](/cases/automations)
## Best Practices
**Keep option lists manageable:**
* 3-12 options is ideal
* More than 20 options: consider search functionality or text input
* Group logically (alphabetically or by frequency)
**Use clear, distinct labels:**
* Each option should be clearly different
* Avoid similar or overlapping choices
* Use consistent terminology
**Always include "Other" when appropriate:**
* Provides escape hatch for unlisted options
* Prevents forcing users into wrong category
* Can trigger follow-up for details
**Value vs Label clarity:**
* Values: short, technical, consistent (e.g., `belgium`)
* Labels: friendly, readable (e.g., "Belgium")
* Values don't change, labels can be updated
**Order options logically:**
* Alphabetical for countries and names
* Most common first for frequency-based
* Logical progression for priority/urgency
* Group related options together
**Set defaults carefully:**
* Only set default if there's a clear common choice
* Don't default to first option (users may not notice)
* Leave blank when user should actively choose
**Required vs Optional:**
* Mark required when choice is essential
* Leave optional when "prefer not to say" is valid
* Consider if "no selection" is a valid state
***
## Related Field Types
Use for selecting multiple options
Use for simple yes/no only
Use when predefined options aren't suitable
Back to Data Schema overview
# Currency Field
Source: https://docs.penbox.io/cases/data_schema/currency
Monetary values with currency formatting for financial amounts
The Currency field captures monetary values — amounts with two decimal places and optional prefix/suffix for currency symbols. Currency fields are purpose-built for financial data, ensuring consistent formatting and validation for prices, costs, revenues, and claims.
Use Currency fields whenever you need to store monetary amounts. They enforce two-decimal precision and support min/max validation, making them ideal for any financial data point.
## When to Use
**Use Currency fields for:**
* Prices and costs
* Revenue and income
* Claim amounts
* Loan amounts
* Insurance premiums
* Fees and charges
* Any monetary value
**Consider alternatives:**
* Use **Number** for non-monetary numeric values (quantities, scores, measurements)
* Use **Text** for formatted financial references (invoice numbers, account IDs)
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `claim_amount` |
| `name` | Display label | String | Required | "Claim Amount" |
| `description` | Help text for users | String | Optional | "Total claim value in euros" |
| `min` | Minimum allowed value | Number | None | `0` |
| `max` | Maximum allowed value | Number | None | `1000000` |
| `prefix` | Text displayed before the value | String | None | `EUR` |
| `suffix` | Text displayed after the value | String | None | `EUR` |
| `clearable` | Allow clearing the field | Boolean | `true` | `false` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Basic Currency Amount
Collect a monetary value:
```json theme={null}
{
"key": "claim_amount",
"type": "currency",
"name": "Claim Amount",
"description": "Total claim value in euros",
"suffix": "EUR",
"min": 0,
"max": 1000000
}
```
### Annual Revenue
Collect revenue with prefix:
```json theme={null}
{
"key": "annual_revenue",
"type": "currency",
"name": "Annual Revenue",
"description": "Company revenue for last fiscal year",
"prefix": "EUR",
"min": 0
}
```
### Insurance Premium
Collect premium amount:
```json theme={null}
{
"key": "gross_premium",
"type": "currency",
"name": "Gross Premium",
"description": "Total gross premium amount",
"suffix": "EUR",
"min": 0
}
```
## Validation
Currency fields enforce monetary validation automatically:
### Type Validation
* Only numeric values accepted
* Maximum two decimal places (cents precision)
* Non-numeric input rejected
### Range Validation
* `min` - Minimum allowed value (inclusive)
* `max` - Maximum allowed value (inclusive)
* User cannot enter values outside range
Currency fields always enforce a maximum of 2 decimal places, ensuring
consistent monetary precision.
## Best Practices
**Always use Currency for monetary data:**
* Don't use Number for financial amounts
* Currency enforces two-decimal precision automatically
* Ensures consistent formatting across the platform
**Use prefix or suffix for currency indication:**
* Add `prefix: "EUR"` or `suffix: "EUR"` to indicate the currency
* Helps users understand the expected currency
* Be consistent across all currency fields in a case
**Set appropriate min/max:**
* Use `min: 0` to prevent negative amounts (when appropriate)
* Set realistic max values based on business context
* Catches data entry errors early
**Be explicit in field names:**
* Include currency in the name when relevant: "Amount (EUR)"
* Distinguish between gross and net amounts
* Clarify the time period: "Annual Revenue" vs "Monthly Revenue"
***
## Related Field Types
Use for non-monetary numeric values
Use for financial reference numbers and IDs
Back to Data Schema overview
# Date Field
Source: https://docs.penbox.io/cases/data_schema/date
Date picker for birth dates, deadlines, and timestamps
The Date field provides a date picker interface for capturing date values. Date fields enable date-based comparisons, calculations, and automations, making them essential for deadlines, birth dates, start dates, and any time-sensitive data.
Use Date fields whenever you need to track when something happened or when something is due. Date fields provide built-in validation, consistent formatting, and enable powerful date-based automations and filtering.
## When to Use
**Use Date fields for:**
* Birth dates and ages
* Deadlines and due dates
* Start and end dates
* Incident or claim dates
* Contract dates (signature, expiration)
* Submission dates
* Approval dates
* Any date that drives workflow logic
**Consider alternatives:**
* Use **Text** for date ranges or free-form date descriptions
* Use **Number** for year-only values that don't represent actual dates
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `birth_date` |
| `name` | Display label | String | Required | "Date of Birth" |
| `description` | Help text for users | String | Optional | "Your date of birth" |
| `min` | Earliest allowed date | Date string | None | `1900-01-01` |
| `max` | Latest allowed date | Date string | None | `2026-12-31` |
| `format` | Display format | String | `YYYY-MM-DD` | `DD/MM/YYYY` |
| `default` | Default value | Date string | None | `today`, `2026-01-01` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Birth Date
Collect date of birth with reasonable validation:
```json theme={null}
{
"key": "birth_date",
"type": "date",
"name": "Date of Birth",
"description": "Your date of birth",
"min": "1900-01-01",
"max": "today"
}
```
### Deadline Date
Set a future deadline:
```json theme={null}
{
"key": "submission_deadline",
"type": "date",
"name": "Submission Deadline",
"description": "Documents must be submitted by this date",
"min": "today",
"default": "+30days"
}
```
### Incident Date
Collect when an event occurred:
```json theme={null}
{
"key": "incident_date",
"type": "date",
"name": "Incident Date",
"description": "When did the incident occur?",
"max": "today"
}
```
### Contract Expiration
Track contract end date:
```json theme={null}
{
"key": "contract_expiration",
"type": "date",
"name": "Contract Expiration Date",
"description": "When this contract expires",
"min": "today"
}
```
## Validation
Date fields provide automatic validation:
### Format Validation
* Only valid dates accepted
* Prevents impossible dates (February 30, month 13, etc.)
* Handles leap years correctly
* Consistent date format across platform
### Range Validation
* `min` - Earliest allowed date
* `max` - Latest allowed date
* Use `today` as dynamic value for current date
* Use relative values like `+30days`, `-1year`
### Special Values
* `today` - Current date
* `+Ndays` - N days from today (e.g., `+30days`)
* `-Ndays` - N days before today (e.g., `-7days`)
* `+Nmonths` - N months from today
* `+Nyears` - N years from today
## Date-Based Automations
Date fields enable powerful date-based automations:
**Deadline reminders:**
* Send reminder 7 days before deadline
* Escalate when deadline passes
* Update status based on deadline proximity
**Age calculations:**
* Calculate age from birth date
* Determine eligibility based on age
* Trigger different workflows for age groups
**Time-based transitions:**
* Move to "Overdue" if response not received by deadline
* Archive cases after 90 days from closure date
* Trigger renewal workflow 30 days before expiration
**Date comparisons:**
* Ensure start date is before end date
* Check if incident date is within policy coverage period
* Validate dates fall within reasonable ranges
[Learn more about Automations →](/cases/automations)
## Best Practices
**Use Date fields for all date values:**
* Don't store dates as text (prevents date-based logic)
* Enables date calculations and comparisons
* Provides consistent date formatting
**Set reasonable min/max:**
* Prevents clearly invalid dates
* Use `today` for dynamic validation
* Consider use case (birth dates should be in past)
**Default values:**
* Use `today` for current date fields
* Use `+30days` for typical deadlines
* Leave blank when user must actively choose
**Consider timezone:**
* Dates are stored in ISO 8601 format
* Display format respects user locale
* Specify timezone requirements in description if critical
***
## Related Field Types
Use for year-only values that aren't specific dates
Use for date ranges or free-form date descriptions
Create date-based workflow automations
Back to Data Schema overview
# File Field
Source: https://docs.penbox.io/cases/data_schema/file
File upload and storage for documents, images, and attachments
The File field enables file uploads and storage within cases. Users can upload documents, images, PDFs, contracts, and other files directly into the case. File fields integrate with Document Intelligence for automatic data extraction from uploaded documents.
Use File fields whenever you need to collect documents as part of the case workflow — identity documents, contracts, invoices, receipts, images, or any file-based information.
## When to Use
**Use File fields for:**
* Identity documents (ID cards, passports, driver's licenses)
* Contracts and agreements
* Invoices and receipts
* Proof documents (address, income, employment)
* Images and photos (damage, products, signatures)
* Medical documents
* Tax documents
* Certificates and licenses
* Supporting documentation
* Signed documents
**Consider alternatives:**
* Use **Text** for URLs to external documents
* Use **API Call** to fetch documents from external systems
* Use document generation for creating documents (not collecting them)
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `identity_document` |
| `name` | Display label | String | Required | "Identity Document" |
| `description` | Help text for users | String | Optional | "Upload ID card or passport" |
| `accept` | Allowed file types (MIME types) | Array | All files | `["application/pdf", "image/*"]` |
| `max_size` | Maximum file size (bytes) | Number | `10485760` | `5242880` (5MB) |
| `max_files` | Maximum number of files | Number | `1` | `5` |
| `required` | Must upload at least one file | Boolean | `false` | `true` |
| `enable_intelligence` | Enable Document Intelligence | Boolean | `false` | `true` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Identity Document
Collect government ID:
```json theme={null}
{
"key": "identity_document",
"type": "file",
"name": "Identity Document",
"description": "Upload your ID card, passport, or driver's license",
"required": true,
"accept": ["image/*", "application/pdf"],
"max_size": 5242880,
"max_files": 2,
"enable_intelligence": true
}
```
### Invoice Upload
Collect invoices:
```json theme={null}
{
"key": "invoices",
"type": "file",
"name": "Invoices",
"description": "Upload relevant invoices (PDF format preferred)",
"accept": ["application/pdf", "image/*"],
"max_files": 10,
"enable_intelligence": true
}
```
### Damage Photos
Collect images of damage:
```json theme={null}
{
"key": "damage_photos",
"type": "file",
"name": "Damage Photos",
"description": "Upload clear photos showing the damage",
"required": true,
"accept": ["image/jpeg", "image/png", "image/heic"],
"max_size": 10485760,
"max_files": 20
}
```
### Contract Document
Collect signed contract:
```json theme={null}
{
"key": "signed_contract",
"type": "file",
"name": "Signed Contract",
"description": "Upload the signed contract document",
"required": true,
"accept": ["application/pdf"],
"max_files": 1,
"enable_intelligence": true
}
```
## File Type Restrictions
Use the `accept` option to restrict file types:
### Common MIME Types
| Type | MIME Type | Example |
| - | - | - |
| PDF | `application/pdf` | Contracts, forms |
| Images (all) | `image/*` | Photos, scans |
| JPEG | `image/jpeg` | Photos |
| PNG | `image/png` | Screenshots, graphics |
| Word | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | .docx files |
| Excel | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` | .xlsx files |
| All files | `*/*` | No restriction |
**Examples:**
Only PDFs:
```json theme={null}
{ "accept": ["application/pdf"] }
```
PDFs and images:
```json theme={null}
{ "accept": ["application/pdf", "image/*"] }
```
Images only:
```json theme={null}
{ "accept": ["image/*"] }
```
File type restrictions are enforced client-side for user experience but should
not be relied upon for security. Always validate file types server-side.
## File Size Limits
Control maximum file size with `max_size` (in bytes):
| Limit | Bytes | Configuration |
| - | - | - |
| 1 MB | 1,048,576 | `"max_size": 1048576` |
| 5 MB | 5,242,880 | `"max_size": 5242880` |
| 10 MB | 10,485,760 | `"max_size": 10485760` |
| 20 MB | 20,971,520 | `"max_size": 20971520` |
**Default:** 10 MB (10,485,760 bytes)
Set appropriate file size limits based on expected document types. ID scans:
5MB is sufficient. High-res photos: 10-20MB may be needed.
## Multiple Files
Allow multiple file uploads with `max_files`:
**Single file only:**
```json theme={null}
{ "max_files": 1 }
```
**Up to 5 files:**
```json theme={null}
{ "max_files": 5 }
```
**Unlimited files:**
```json theme={null}
{ "max_files": null }
```
Use multiple files for:
* Multiple pages of same document
* Several related documents
* Multiple photos of damage
* Various supporting documents
## Document Intelligence Integration
Enable automatic data extraction with `enable_intelligence: true`:
When enabled, Document Intelligence:
* Automatically extracts data from uploaded documents
* Populates other fields in the case based on extracted data
* Validates extracted data against case schema
* Flags inconsistencies or missing information
**What Intelligence extracts:**
* Text content from PDFs
* Structured data (names, dates, amounts, addresses)
* Information from scanned documents and images
* Data from forms and tables
* Key-value pairs from documents
[Learn more about Document Intelligence →](/intelligence/document-intelligence)
## File Storage
Uploaded files are:
* Stored securely within the case
* Accessible to case members
* Included in case exports
* Retained according to workspace retention policies
* Visible in the case timeline when uploaded
**File metadata stored:**
* Original filename
* File size
* Upload timestamp
* Uploaded by (member)
* File type (MIME)
## Validation
File fields enforce upload validation:
### Required Validation
* If `required: true`, at least one file must be uploaded
* User cannot proceed without uploading
### File Type Validation
* Only specified MIME types accepted
* Rejected files show error message
### Size Validation
* Files exceeding `max_size` rejected
* Clear error message shown
### Count Validation
* Cannot exceed `max_files`
* Disable upload when limit reached
## Best Practices
**Set appropriate file types:**
* Be specific when possible (PDFs only for contracts)
* Allow both PDF and images for flexibility (scans, photos)
* Don't be overly restrictive (users may have various formats)
**Configure realistic size limits:**
* Consider document type (ID scans: 5MB, photos: 10-20MB)
* Too restrictive: frustrates users with valid files
* Too generous: allows unnecessarily large uploads
**Use clear descriptions:**
* Specify accepted formats in description
* Mention size limits if restrictive
* Explain what to upload ("front and back of ID")
**Enable Intelligence strategically:**
* Enable for documents with extractable data
* Not necessary for photos without text
* Reduces manual data entry significantly
**Multiple files configuration:**
* Single file: official documents, contracts
* Multiple files: supporting docs, damage photos
* Consider workflow: easier to upload multiple at once
**Organize with sections:**
* Group related file fields in sections
* "Identity Documents", "Financial Documents", "Photos"
* Makes case data easier to navigate
**Name fields descriptively:**
* "Identity Document" not just "Document"
* "Damage Photos" not just "Photos"
* Helps users and case managers understand purpose
***
## Related Field Types
Use for URLs to external documents
Use to fetch documents from external systems
Learn about automatic data extraction
Back to Data Schema overview
# Multiple Choice Field
Source: https://docs.penbox.io/cases/data_schema/multiple_choice
Multiple selections from predefined options
The Multiple Choice field allows users to select multiple items from a predefined list of options. This field type displays as a checkbox group where users can check any combination of the available options.
Use Multiple Choice fields when you need to collect multiple selections from a fixed set of possibilities — services required, features selected, categories, interests, or any scenario where "select all that apply" is appropriate.
## When to Use
**Use Multiple Choice fields for:**
* Services or products selected
* Features or options enabled
* Categories or tags
* Interests or preferences
* Coverage types or insurance products
* Document types required
* Affected areas or departments
* Applicable regulations or requirements
**Consider alternatives:**
* Use **Choices** for single selection only
* Use **Checkbox** for a single yes/no option
* Use **Text** for free-form input
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `services_required` |
| `name` | Display label | String | Required | "Services Required" |
| `description` | Help text for users | String | Optional | "Select all services you need" |
| `options` | Array of available options | Array | Required | See below |
| `min_selections` | Minimum selections required | Number | `0` | `1` |
| `max_selections` | Maximum selections allowed | Number | None | `3` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
### Options Configuration
Each option in the `options` array:
| Property | Description | Type | Required | Example |
| - | - | - | - | - |
| `value` | Internal value stored | String | Yes | `health_insurance` |
| `label` | Display text shown to user | String | Yes | "Health Insurance" |
## Examples
### Services Selection
Allow multiple service selections:
```json theme={null}
{
"key": "services_required",
"type": "multiple_choice",
"name": "Services Required",
"description": "Select all services you need",
"min_selections": 1,
"options": [
{ "value": "life_insurance", "label": "Life Insurance" },
{ "value": "health_insurance", "label": "Health Insurance" },
{ "value": "property_insurance", "label": "Property Insurance" },
{ "value": "liability_insurance", "label": "Liability Insurance" },
{ "value": "pension_planning", "label": "Pension Planning" }
]
}
```
### Document Types
Select required documents:
```json theme={null}
{
"key": "required_documents",
"type": "multiple_choice",
"name": "Required Documents",
"description": "Select all documents you need to submit",
"options": [
{ "value": "id_card", "label": "ID Card or Passport" },
{ "value": "proof_of_address", "label": "Proof of Address" },
{ "value": "bank_statement", "label": "Bank Statement" },
{ "value": "tax_return", "label": "Tax Return" },
{ "value": "employment_contract", "label": "Employment Contract" }
]
}
```
### Coverage Areas
Select applicable coverage:
```json theme={null}
{
"key": "coverage_areas",
"type": "multiple_choice",
"name": "Coverage Areas",
"description": "Select all areas where coverage is needed",
"max_selections": 3,
"options": [
{ "value": "europe", "label": "Europe" },
{ "value": "north_america", "label": "North America" },
{ "value": "asia", "label": "Asia" },
{ "value": "africa", "label": "Africa" },
{ "value": "south_america", "label": "South America" },
{ "value": "oceania", "label": "Oceania" }
]
}
```
## Validation
Multiple Choice fields enforce selection validation:
### Selection Count
* `min_selections` - Minimum required selections
* `max_selections` - Maximum allowed selections
* User must select within range to proceed
### Option Validation
* User can only select from predefined options
* No free-form input
* Ensures data consistency
**Common configurations:**
| Configuration | Use Case |
| - | - |
| `min: 1` | "Select at least one" |
| `min: 1, max: 3` | "Select 1-3 options" |
| `min: 0` | Optional selections (default) |
| `max: 5` | Limit selections |
## Stored Values
Multiple Choice fields store an array of selected values:
**Example stored data:**
```json theme={null}
{
"services_required": [
"life_insurance",
"health_insurance",
"pension_planning"
]
}
```
**Empty state:**
```json theme={null}
{
"services_required": []
}
```
This format enables easy checking in automations and data processing.
## Multiple Choice in Automations
Multiple Choice fields enable conditional logic based on selections:
**Check if specific option selected:**
```json theme={null}
{
"condition": {":in": "life_insurance", "{data.services_required}"}
}
```
**Check if any selection made:**
```json theme={null}
{
"condition": {":gt": {":len": "{data.services_required}"}, "0"}
}
```
**Check if multiple selections:**
```json theme={null}
{
"condition": {":gte": {":len": "{data.services_required}"}, "2"}
}
```
**Trigger based on selection:**
* Show additional steps for specific services
* Route to specialist based on selections
* Calculate pricing based on options selected
* Send targeted information based on interests
[Learn more about Automations →](/cases/automations)
## Best Practices
**Keep option lists focused:**
* 3-8 options is ideal
* More than 12 options becomes overwhelming
* Group related options logically
* Order by frequency of use or alphabetically
**Use clear labels:**
* Labels should be self-explanatory
* Avoid jargon or abbreviations
* Be consistent with terminology
**Consider min/max:**
* Use `min: 1` when at least one selection required
* Use `max` to prevent over-selection
* Most use cases don't need max limit
**Name the field as a question:**
* "Services Required" → clear what to select
* "Select Your Preferences" → action-oriented
* Avoid vague labels like "Options"
**Value vs Label:**
* Use short, technical values (`life_insurance`)
* Use friendly, readable labels ("Life Insurance")
* Values are used in code, labels shown to users
**Default selections:**
* Usually best to have no defaults
* Let users make explicit choices
* Pre-selecting can create unwanted selections
***
## Related Field Types
Use for single selection from options
Use for single yes/no option
Use for free-form input without predefined options
Back to Data Schema overview
# Number Field
Source: https://docs.penbox.io/cases/data_schema/number
Numeric values with formatting for amounts, quantities, and calculations
The Number field captures numeric values — integers or decimals — with optional formatting and validation. Number fields enable calculations, comparisons, and numeric operations within automations and data processing.
Use Number fields whenever you need to perform mathematical operations, apply min/max validation, or format values with prefix/suffix. Number fields are essential for quantities, ages, scores, and any data that requires numeric processing.
## When to Use
**Use Number fields for:**
* Quantities (items, count, volume)
* Ages and years
* Scores and ratings
* Percentages and ratios
* Measurements (weight, height, distance)
* Identifiers that are purely numeric (account numbers, IDs)
* Calculated values
**Consider alternatives:**
* Use **Currency** for monetary amounts (prices, costs, revenues, claims)
* Use **Text** for alphanumeric IDs (contain letters)
* Use **Choices** for predefined numeric ranges (age groups, price tiers)
* Use **Date** for years that represent dates
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `number_of_employees` |
| `name` | Display label | String | Required | "Number of Employees" |
| `description` | Help text for users | String | Optional | "Total full-time employees" |
| `min` | Minimum allowed value | Number | None | `0` |
| `max` | Maximum allowed value | Number | None | `1000000` |
| `decimals` | Number of decimal places | Number | None | `2` |
| `prefix` | Text displayed before the value | String | None | `$` |
| `suffix` | Text displayed after the value | String | None | `kg` |
| `placeholder` | Hint text shown in empty field | String | None | "Enter a number" |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Basic Integer
Collect a simple count:
```json theme={null}
{
"key": "number_of_employees",
"type": "number",
"name": "Number of Employees",
"description": "Total full-time employees",
"min": 1
}
```
### Decimal Value
Collect precise measurements:
```json theme={null}
{
"key": "weight_kg",
"type": "number",
"name": "Weight (kg)",
"description": "Weight in kilograms",
"decimals": 2,
"suffix": "kg",
"min": 0
}
```
### Percentage with Prefix/Suffix
Collect a percentage value:
```json theme={null}
{
"key": "completion_percentage",
"type": "number",
"name": "Completion Percentage",
"description": "Project completion percentage",
"suffix": "%",
"min": 0,
"max": 100,
"decimals": 1
}
```
## Validation
Number fields enforce numeric validation automatically:
### Type Validation
* Only numeric values accepted
* Decimals allowed based on `decimals` configuration
* Non-numeric input rejected
### Range Validation
* `min` - Minimum allowed value (inclusive)
* `max` - Maximum allowed value (inclusive)
* User cannot enter values outside range
### Decimal Validation
* `decimals` defines maximum decimal places allowed
* For integers: omit `decimals` or set to `0`
* For precise values: set `decimals` to the required precision (e.g., `2`)
Validation happens when users fill the field. Existing data or API imports are
not automatically validated.
## Best Practices
**Always use Number for numeric data:**
* Don't store numbers as text (prevents calculations)
* Use Number even if you don't need calculations now (future-proofs)
* Enables numeric comparisons in automations
**Set appropriate min/max:**
* Prevents invalid or unrealistic values
* Catches data entry errors
* Useful for regulated fields with limits
**Configure decimals based on needs:**
* Omit `decimals` for counts and quantities
* Use `2` for precise measurements
* Use appropriate precision for your domain
**Use prefix/suffix for display context:**
* Add `suffix: "kg"` for weight fields
* Add `suffix: "%"` for percentage fields
* Helps users understand expected format
**Use Currency for monetary values:**
* Don't use Number with a currency prefix for financial amounts
* The **Currency** field type is purpose-built for monetary values
**Validate reasonably:**
* Don't set overly restrictive min/max
* Consider edge cases (zero values, large amounts)
* Test with realistic data ranges
***
## Related Field Types
Use for monetary amounts with currency formatting
Use for alphanumeric IDs and formatted numbers
Use for predefined numeric ranges or tiers
Back to Data Schema overview
# Signature Field
Source: https://docs.penbox.io/cases/data_schema/signature
Digital signature capture for legally binding electronic signatures
The Signature field enables digital signature collection directly within cases. This field type captures legally binding electronic signatures with multiple authentication methods, creating a complete audit trail for compliance and verification.
Use Signature fields when you need to collect formal agreements, approvals, or legally binding signatures as part of your case workflow — contracts, consent forms, acknowledgments, or any document requiring a verified signature.
## When to Use
**Use Signature fields for:**
* Contract signatures
* Agreement and consent forms
* Document approvals
* Legal acknowledgments
* Financial authorizations
* Medical consent
* Terms and conditions acceptance (when signature required)
* Compliance acknowledgments
* Verification of identity
* Multi-party agreements
**Consider alternatives:**
* Use **Checkbox** for simple agreement checkboxes (non-signature)
* Use **File** for uploading pre-signed documents
* Use document generation with signatures for template-based signing
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `contract_signature` |
| `name` | Display label | String | Required | "Contract Signature" |
| `description` | Help text for signers | String | Optional | "Sign to accept the contract terms" |
| `required` | Must be signed to proceed | Boolean | `false` | `true` |
| `signer_role` | Who signs this field | String | `main_contact` | `secondary_contact`, `case_manager` |
| `authentication` | Authentication method | String | `handwritten` | `sms_otp`, `email_otp`, `itsme`, `swisscom` |
| `document` | Document to sign | String | None | Document UUID or variable |
| `signature_type` | Type of signature | String | `standard` | `qualified`, `advanced` |
| `sequence` | Signing order (for multiple signatures) | Number | `1` | `2`, `3` |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Authentication Methods
Signature fields support multiple authentication methods for different legal and security requirements:
| Method | Description | Use Case | Legal Level |
| - | - | - | - |
| `handwritten` | Draw signature with mouse/touch | Simple agreements, internal approvals | Basic |
| `email_otp` | One-time code via email | Remote signing, email verification | Standard |
| `sms_otp` | One-time code via SMS | Strong verification, phone validation | Standard |
| `itsme` | Belgian eID authentication | Belgian residents, strong identity verification | Advanced/Qualified |
| `swisscom` | Swiss qualified signature | Swiss residents, legally binding in Switzerland | Qualified |
**Legal signature levels:**
* **Basic**: Simple electronic signature (handwritten)
* **Standard**: Verified email/phone with OTP
* **Advanced**: Identity-verified signature (Itsme)
* **Qualified**: Highest legal level, equivalent to handwritten (Itsme, Swisscom)
## Examples
### Simple Contract Signature
Basic signature for contract acceptance:
```json theme={null}
{
"key": "contract_signature",
"type": "signature",
"name": "Contract Signature",
"description": "Sign below to accept the contract terms",
"required": true,
"signer_role": "main_contact",
"authentication": "handwritten",
"document": "{document.contract_generated}"
}
```
### SMS-Verified Signature
Signature with SMS one-time code authentication:
```json theme={null}
{
"key": "authorization_signature",
"type": "signature",
"name": "Authorization Signature",
"description": "Sign to authorize this transaction. You will receive a code via SMS.",
"required": true,
"signer_role": "main_contact",
"authentication": "sms_otp",
"signature_type": "advanced"
}
```
### Multi-Party Signing Sequence
Multiple signatures in specific order:
```json theme={null}
{
"key": "client_signature",
"type": "signature",
"name": "Client Signature",
"description": "Client signature on agreement",
"required": true,
"signer_role": "main_contact",
"authentication": "email_otp",
"sequence": 1,
"document": "{document.agreement}"
}
```
```json theme={null}
{
"key": "manager_signature",
"type": "signature",
"name": "Manager Approval",
"description": "Manager approval signature",
"required": true,
"signer_role": "case_manager",
"authentication": "handwritten",
"sequence": 2,
"document": "{document.agreement}"
}
```
### Qualified Electronic Signature (Itsme)
Legally binding qualified signature for Belgian residents:
```json theme={null}
{
"key": "qualified_signature",
"type": "signature",
"name": "Qualified Electronic Signature",
"description": "Sign using your Itsme account for legally binding signature",
"required": true,
"signer_role": "main_contact",
"authentication": "itsme",
"signature_type": "qualified",
"document": "{document.legal_agreement}"
}
```
## Signer Roles
Control who signs each signature field:
| Role | Who Signs | Use Case |
| - | - | - |
| `main_contact` | Primary case contact | Client signatures, customer agreements |
| `secondary_contact` | Additional contact | Co-signers, witnesses, secondary parties |
| `case_manager` | Case owner/manager | Internal approvals, manager authorization |
| `custom` | Specified via variable | Dynamic signer assignment |
**Multiple signers:**
* Use `sequence` to control signing order
* Each signer must complete before next can sign
* Useful for approval chains and multi-party agreements
## Signature Workflow
### Signature Request Process
1. **Request sent**: Signer receives notification (email/SMS)
2. **Authentication**: Signer verifies identity (method depends on configuration)
3. **Review**: Signer reviews document (if attached)
4. **Sign**: Signer provides signature (draw, type, or authenticated)
5. **Confirmation**: Signature captured and stored
6. **Complete**: All parties notified, document finalized
### Signature Metadata Stored
Each signature captures:
* Signer name and contact information
* Timestamp (date and time signed)
* IP address (for audit trail)
* Authentication method used
* Device information
* Signature image (if handwritten)
* Certificate information (if qualified)
### Document Attachment
Signatures can be attached to documents:
* Generated documents from templates
* Pre-uploaded documents
* Documents from other steps
* Multiple signatures on same document
## Validation
Signature fields enforce signing validation:
### Required Validation
* If `required: true`, signature must be completed
* Case cannot progress until signed
* Useful for mandatory agreements
### Authentication Validation
* OTP codes must match (email/SMS)
* Identity must be verified (Itsme, Swisscom)
* Prevents unauthorized signatures
### Sequence Validation
* Earlier sequence signatures must complete first
* Order enforced automatically
* Prevents out-of-order signing
## Compliance and Legal
### Audit Trail
Complete audit trail for each signature:
* Who signed (name, email, phone)
* When signed (timestamp with timezone)
* Where signed (IP address, location)
* How authenticated (method used)
* What was signed (document version)
### Legal Validity
Signature validity depends on authentication method:
* **Handwritten**: Valid for low-risk agreements
* **Email/SMS OTP**: Valid for verified identity
* **Itsme/Swisscom**: Legally equivalent to handwritten signature in EU
### GDPR Compliance
Signature data is:
* Stored securely and encrypted
* Retained according to legal requirements
* Included in data export requests
* Deleted when case deleted (with retention rules)
### eIDAS Regulation
Qualified signatures (Itsme, Swisscom) comply with:
* eIDAS Regulation (EU 910/2014)
* Legally binding across EU member states
* Equivalent to handwritten signatures
* Admissible as legal evidence
## Best Practices
**Choose appropriate authentication:**
* Handwritten: Simple agreements, internal documents
* Email OTP: Remote signing, email verification needed
* SMS OTP: Higher security, phone verification
* Itsme/Swisscom: Legal requirements, high-value agreements
**Set realistic requirements:**
* Only mark required if signature truly mandatory
* Consider fallback if authentication fails
* Provide clear instructions for signers
**Sequence multiple signatures logically:**
* Client signs first, then internal approval
* Primary signer before secondary signers
* Lower authority before higher authority
**Attach the right documents:**
* Attach document being signed
* Ensure document is finalized before signing
* Use generated documents for consistency
**Provide clear descriptions:**
* Explain what signer is agreeing to
* Mention authentication method required
* Indicate estimated time to complete
**Test authentication methods:**
* Test OTP delivery (email/SMS)
* Verify Itsme/Swisscom integration
* Check user experience on mobile devices
**Consider user experience:**
* Mobile-friendly signature methods
* Clear error messages for failed authentication
* Allow signature retry on failure
* Send reminders for pending signatures
**Maintain compliance:**
* Store audit trail for legal minimum period
* Document authentication methods used
* Regularly review signature processes
* Ensure GDPR compliance for signature data
***
## Related Field Types
Use for simple agreement checkboxes
Use for uploading pre-signed documents
Learn about generating documents for signing
Back to Data Schema overview
# Text Field
Source: https://docs.penbox.io/cases/data_schema/text
Single or multi-line text input for names, descriptions, and IDs
The Text field is the most versatile field type in Penbox. It captures single-line or multi-line text input, making it ideal for names, addresses, descriptions, notes, IDs, and any other free-form text data.
Text fields can be configured with character limits, validation patterns, and placeholder hints. They support both simple input (like a name) and complex multi-line content (like detailed notes or descriptions).
## When to Use
**Use Text fields for:**
* Names (person, company, product)
* Addresses (street, city, full address)
* Descriptions and notes
* IDs and reference numbers
* Email addresses (simple text, or use Email element in forms)
* Phone numbers (simple text, or use Phone element in forms)
* URLs and website addresses
* Comments and feedback
* Any free-form text input
**Consider alternatives:**
* Use **Number** for numeric values (enables calculations)
* Use **Date** for dates (enables date-based features)
* Use **Choices** for predefined options (ensures consistency)
## Configuration Options
| Option | Description | Type | Default | Example |
| - | - | - | - | - |
| `key` | Unique identifier | String | Required | `company_name` |
| `name` | Display label | String | Required | "Company Name" |
| `description` | Help text for users | String | Optional | "Legal name of your company" |
| `multiline` | Enable multi-line textarea | Boolean | `false` | `true` |
| `maxlength` | Maximum character count | Number | None | `500` |
| `pattern` | Regex validation pattern | String | None | `^[A-Z0-9]+$` |
| `placeholder` | Hint text shown in empty field | String | None | "Enter company name" |
| `visibility` | Display setting | String | `always-visible` | `hide-when-empty` |
| `section` | Section this field belongs to | String | None | Section UUID |
## Examples
### Basic Single-Line Text
Collect a company name:
```json theme={null}
{
"key": "company_name",
"type": "text",
"name": "Company Name",
"description": "Official registered name of your company"
}
```
### Multi-Line Text (Notes)
Collect detailed notes or descriptions:
```json theme={null}
{
"key": "claim_description",
"type": "text",
"name": "Claim Description",
"description": "Provide detailed description of the incident",
"multiline": true,
"maxlength": 2000,
"placeholder": "Describe what happened, when it occurred, and any relevant details..."
}
```
### Validated Text (IBAN)
Collect structured text with validation:
```json theme={null}
{
"key": "iban",
"type": "text",
"name": "IBAN",
"description": "International Bank Account Number",
"pattern": "^[A-Z]{2}[0-9]{2}[A-Z0-9]+$",
"placeholder": "BE68539007547034"
}
```
### Hidden Text Field (System Use)
Store data not shown to users:
```json theme={null}
{
"key": "external_crm_id",
"type": "text",
"name": "CRM ID",
"description": "External system identifier",
"visibility": "always-hide"
}
```
## Validation
Text fields support several validation mechanisms:
### Character Length
Use `maxlength` to limit input:
* Prevents overly long entries
* Ensures data fits in external systems
* Useful for regulated fields with character limits
```json theme={null}
{
"maxlength": 500
}
```
### Pattern Matching
Use `pattern` for regex validation:
* Format validation (IBANs, postal codes, IDs)
* Character restrictions (alphanumeric only)
* Structure enforcement
**Common patterns:**
| Use Case | Pattern | Example |
| - | - | - |
| Alphanumeric only | `^[A-Z0-9]+$` | ABC123 |
| Email format | `^[^\s@]+@[^\s@]+\.[^\s@]+$` | [user@domain.com](mailto:user@domain.com) |
| Phone (simple) | `^\+?[0-9\s\-\(\)]+$` | +1 (555) 123-4567 |
| Postal code (BE) | `^[0-9]{4}$` | 1000 |
| IBAN | `^[A-Z]{2}[0-9]{2}[A-Z0-9]+$` | BE68539007547034 |
Pattern validation only applies when users fill the field. It doesn't validate
existing data or data from API imports.
## Best Practices
**Use descriptive names:**
* "Company Legal Name" is better than "Name"
* "Incident Description" is better than "Description"
* Clear names help users and Intelligence understand what's expected
**Add help text:**
* Use the `description` field to guide users
* Explain format requirements, especially for validated fields
* Mention if the field is required for compliance
**Choose single vs multi-line appropriately:**
* Single-line for short, structured text (names, IDs, codes)
* Multi-line for longer content (descriptions, notes, comments)
* Set appropriate `maxlength` for multi-line to prevent excessive input
**Use validation wisely:**
* Only validate when necessary (IBANs, IDs, codes)
* Don't over-validate (strict patterns can frustrate users)
* Test patterns thoroughly before deploying
**Consider visibility:**
* Use "hide when empty" for optional supplementary fields
* Use "always hide" for system IDs and technical data
* Keep essential fields always visible
***
## Related Field Types
Use for numeric values that need calculations
Use when input should be from predefined options
Use for date values to enable date-based features
Back to Data Schema overview
# What are Cases
Source: https://docs.penbox.io/cases/introduction
The core of the Case Intelligence platform
A **Case** is an intelligent workflow involving a case manager (workspace member) and one or more contacts. Cases combine email, SMS, forms, documents, and data collection into a single managed workflow.
A case can be as simple as sending a single form or email, or as complex as a multi-month process with dozens of steps, multiple contacts, document generation, digital signatures, and intelligent automations. Every case has a timeline with events and steps (the complete journey of what happened and what's next), contacts (people involved), a data schema (information to collect), statuses (where the case stands), and optionally, automations (rules that drive the workflow forward).
Cases don't require a template. You can create a blank case and build it as you go. For recurring workflows, templates let you pre-configure the structure so every new case starts with the right steps, contacts, and data schema already in place.
Cases are the core of Penbox. Sending a form is the simplest action you can perform within a case.
## Main Contact
Every case requires at least one **Main Contact** to function effectively. The main contact is the primary individual the case is about and the recipient for all external communications and interactions.
**The main contact:**
* Receives forms sent from the case
* Receives email communications and notifications
* Is the primary point of contact for the matter
* Can be an individual person or organization representative
* Must have at least an email address or phone number to receive communications
A case without a main contact cannot send forms or communications to external parties. Always add a main contact to enable full case functionality.
You can add additional contacts with different roles as secondary contacts, but the main contact is essential for the case workflow.
If you create a case from a form submission or email, the sender's information is automatically added as the main contact.
[Learn more about Contacts →](/cases/contacts)
## Case Reference
Each case is automatically assigned a unique **6-character alphanumeric reference** (e.g., `AB12XY`) for easy identification and external communication.
**Uses for reference numbers:**
* Quick lookup and search in your system
* Share with customers and partners (instead of internal IDs)
* Reference in emails and customer communications
* Tracking and reporting
* Linking to external systems (CRM, accounting software)
* Regulatory and compliance documentation
* Phone support and customer inquiries
Reference numbers provide a human-friendly way to discuss cases with external parties while maintaining internal organization.
## Case Owner
Every case can be assigned to an **owner** — a team member responsible for managing the case.
**The owner:**
* Receives notifications about case updates
* Can update case data and status
* Is accountable for case progress
* Appears in case history
Assigning clear ownership prevents cases from falling through the cracks and ensures accountability.
## Waiting For Status
Cases track what they're waiting for to help teams prioritize work:
* **None** - No action required, case is progressing
* **Owner** - Waiting for internal team action
* **Contact** - Waiting for external contact response
This status helps you:
* Identify cases that need immediate attention
* Filter cases waiting on external parties
* Prioritize your workload
* Prevent cases from stalling
The "Waiting For" status can be set manually or updated automatically by Intelligence based on email content.
## Case Timeline
Cases automatically track all activities in a chronological timeline:
* Case created
* Status changes
* Field updates
* Emails sent and received
* SMS sent
* Forms sent to contacts
* Form responses received
* Internal notes added
* Workflow steps completed
This creates a complete audit trail of everything that happened with the case.
## Archiving Cases
Cases can be archived when they're no longer actively needed but should be retained:
* Don't appear in default case lists
* Can still be searched and accessed
* Retain all data and history
* Can be unarchived if needed
## Intelligence Features
Penbox includes AI-powered features to help manage cases:
### AI-Generated Case Titles
When enabled, Penbox can automatically generate meaningful case titles based on the case data, making it easier to identify cases at a glance.
### Email Analysis
Penbox automatically analyzes emails sent from cases to:
* Generate concise summaries
* Determine who needs to take the next action
* Update the "Waiting For" status automatically
### Document Intelligence
AI can help extract information from documents and emails to automatically populate case fields. AI descriptions on data points guide the extraction process, helping Intelligence understand what to look for.
***
## Next Steps
Learn how cases are created
Understand fields and data organization
Learn about workflow steps
Design reusable case templates
# Statuses & Lifecycle
Source: https://docs.penbox.io/cases/statuses_lifecycle
The status model and case lifecycle
Every case has a status that indicates its current state in the workflow. Statuses help teams understand where each case is in the process and what needs to happen next. Cases progress through statuses from creation through active management to archiving or closure.
## Default Statuses
Penbox provides four default statuses that serve as the foundation for all case workflows:
Case is being prepared, not yet active
**Common uses:** Initial setup, gathering preliminary info, preparing case before activation
Case is waiting to be started or for an external trigger
**Common uses:** Awaiting documents, awaiting customer action, scheduled to start later
Case is actively being worked on
**Common uses:** Data collection, verification, processing, review
Case is complete
**Common uses:** Approved, completed, rejected, cancelled
## Status Hierarchy
Cases use a hierarchical status system where custom statuses are grouped under the four default parent statuses.
**Why hierarchical?**
* **Flexibility** - Create specific statuses for your workflow
* **Consistency** - All statuses map to standard states
* **Filtering** - Filter by parent status to see all "in progress" cases
* **Reporting** - Group by parent status for high-level metrics
**Example hierarchy:**
```
📁 draft
└─ preparing
└─ review_needed
📁 pending
└─ awaiting_documents
└─ awaiting_approval
└─ awaiting_payment
📁 in_progress
└─ collecting_data
└─ verification
└─ compliance_review
└─ final_approval
📁 closed
└─ approved
└─ completed
└─ rejected
└─ cancelled
```
## Custom Statuses
Case templates can define custom statuses specific to their workflow. Each custom status is defined with:
**Status properties:**
* **Key** - Unique identifier (e.g., `awaiting_documents`, `compliance_review`)
* **Label** - Display name shown to users (e.g., "Awaiting Documents", "Compliance Review")
* **Parent status** - Must be one of: `draft`, `pending`, `in_progress`, `closed`
* **Auto transition** - Optional conditions for automatic status changes
Custom statuses are defined in case templates. Individual cases inherit these statuses and progress through them based on your workflow.
[Learn more about Case Templates →](/cases/case_templates)
## Status Transitions
Cases move through statuses either manually or automatically.
### Manual Status Changes
Team members can change status:
* Select new status from dropdown
* Status updates immediately
* Change is logged in timeline
* Can trigger automations
### Automatic Status Transitions
Statuses can change automatically based on conditions:
* All required fields filled
* Documents uploaded
* Forms completed
* External data received
* Time conditions met
[Learn more about Automations →](/cases/automations)
### Waiting For Indicator
Beyond the status, cases also track "Waiting For" to indicate who needs to act next:
* **None** - No action required, case is progressing
* **Owner** - Waiting for internal team action
* **Contact** - Waiting for external contact response
This indicator helps teams prioritize work and identify cases that need immediate attention. It can be set manually or updated automatically by Intelligence based on email content.
## Case Lifecycle
Cases progress through a defined lifecycle from creation to closure:
### Creation
Cases enter the system through one of four methods:
* **Manually in Penbox** - Team member creates from app
* **From incoming email** - Email Intelligence or dedicated mailbox
* **From completed form** - Form linked to case creation
* **Via API** - Programmatic creation from external systems
New cases typically start in Draft or Pending status.
[Learn more about Case Creation →](/cases/case_creation)
### Active Management
During active management, cases:
* Progress through statuses based on workflow
* Collect data through forms and manual updates
* Execute steps (emails, SMS, forms)
* Trigger automations based on conditions
* Track all activities in timeline
* Update "Waiting For" indicator as needed
Team members work on cases, contacts respond to forms, and automations move the workflow forward.
### Archiving
Cases can be archived when they're no longer actively needed but should be retained:
**Archived cases:**
* Don't appear in default case lists
* Can still be searched and accessed
* Retain all data and history
* Can be unarchived if needed
* Maintain complete audit trail
**When to archive:**
* Case is closed but may need future reference
* Regulatory retention period requires keeping records
* Historical data needed for reporting
* Legal holds or compliance requirements
Archive closed cases to keep your active list clean while maintaining searchability for audits and historical reference.
### Deletion
Cases can only be permanently deleted under specific conditions:
**Deletion rules:**
* **A case can only be permanently deleted before any steps are executed**
* Once steps are executed, the case enters the permanent record
* Executed cases must be archived, not deleted
* This preserves audit trails for compliance
**Why this matters:**
* Maintains regulatory compliance
* Preserves audit trails
* Prevents accidental loss of case history
* Ensures data integrity
Once a case has executed steps (sent emails, forms, SMS), it cannot be permanently deleted. This ensures complete audit trails for compliance and regulatory requirements.
## Status Best Practices
**Use action-oriented names** - Status names should indicate what's happening or what's needed. Good: "Awaiting Customer Response", "Under Review". Avoid: "Status 1", "Phase 2".
**Keep parent status logical** - Choose parent statuses that match the actual case state. Use `pending` when waiting for something external, `in_progress` when team is actively working, `closed` only for final terminal states.
**Don't create too many statuses** - Each status should represent a meaningful stage in your process. Recommended: 5-10 custom statuses per template.
**Make statuses sequential** - If your workflow has clear stages, make statuses follow that sequence. Example: Onboarding → Verification → Review → Approval → Closed.
**Plan for edge cases** - Include statuses for exceptions like "Rejected", "Cancelled", "On Hold" to handle cases that don't complete the standard workflow.
***
## Next Steps
Configure statuses in templates
Automate status transitions
Learn about cases
Understand case data
# Steps
Source: https://docs.penbox.io/cases/steps
The building blocks of a case workflow
Steps are the individual actions that make up a case, executed in sequence or on demand. A step can be an email sent to a contact, an SMS message, a form to collect information, or an internal form completed by the case manager.
For each step type, the configuration determines what it does, what can be customized, and how it appears in the case timeline. Steps can be predefined in a case template or added ad-hoc during case execution.
## Step Types
Cases support four types of steps:
**Outbound email to a contact**
Configure sender, recipients, subject, and body. Can include attachments and form links.
**Outbound text message**
Send quick notifications or updates via SMS to contact phone numbers.
**Send an external form to a contact**
Email containing a link to a form that the contact fills out. Form can be a template or created on-the-fly.
**Form completed by case manager**
Structured data collection for internal workflows, completed by team members rather than external contacts.
## Predefined Steps
When you create a case from a template, predefined workflow steps are available. These steps are configured in the case template with specific fields, forms, or email templates. They appear ready to activate with a single click, ensuring consistency across all cases created from that template.
**Predefined steps:**
* Are defined in the case template
* Include pre-configured content (email templates, form templates, etc.)
* Can be conditional (shown or hidden based on case data)
* Can be automated (triggered when conditions are met)
* Appear in the case as available actions
[Learn more about Case Templates →](/cases/case_templates)
## Ad-Hoc Steps
You can create steps manually at any time while working on a case. Ad-hoc steps are executed immediately and appear in the case timeline once completed.
**Common ad-hoc steps:**
* Send an email to a contact
* Request information via form
* Send an SMS notification
* Complete an internal form
Ad-hoc steps are useful for unique situations that don't fit predefined workflows or for responding to unexpected needs during case management.
## How Steps Appear in the Timeline
All steps are recorded in the case timeline, creating a complete audit trail:
**Email steps show:**
* When the email was sent
* Who it was sent to
* Subject and body content
* Delivery status
* Any attachments included
**SMS steps show:**
* When the SMS was sent
* Recipient phone number
* Message content
* Delivery status
**Form steps show:**
* When the form was sent
* Who it was sent to
* Form template used
* Response status (pending, in progress, completed)
* When response was received
* Collected data
**Internal form steps show:**
* Who completed the form
* When it was completed
* Data collected
Each step includes a timestamp and the team member who executed it, maintaining a complete record of all case interactions.
## Modifying and Deleting Steps
**Modifying steps:**
* Predefined steps can be edited in the case template
* Changes to templates don't affect existing cases
* Individual case steps can't be modified after execution
* Draft steps (not yet executed) can be edited or discarded
**Deleting steps:**
* Executed steps can't be deleted (preserves audit trail)
* Draft steps (not yet sent) can be removed
* Steps appear permanently in timeline once executed
This ensures cases maintain an immutable history for compliance and audit purposes.
## Adding Internal Notes
While there isn't a dedicated "Note" step type, you can add internal context to cases in several ways:
**Internal form steps:**
* Create an internal form for team members to complete
* Use text fields to document decisions, observations, or context
* Captured in the timeline with full auditability
**Email steps to internal addresses:**
* Send emails to internal team members
* Include notes, updates, or requests for review
* Appears in case timeline
**Case data fields:**
* Add notes fields to your case data schema
* Update these fields to document progress
* Changes appear in timeline
For regulatory compliance, use internal form steps to document key decisions. This creates a timestamped, auditable record in the case timeline.
## Step Patterns
### Email with Form
The most common pattern for collecting information from contacts:
1. Create an Email step
2. Write professional email content explaining what you need
3. Attach a form link (from template or quick form)
4. Send to contact
5. Contact receives email, clicks link, completes form
6. Response automatically appears in case
**Best practice:** Always provide context in the email. Don't just send a bare form link.
### Sequential Steps
Multiple steps executed in order, with each step dependent on the previous:
1. **Initial email** - Introduce yourself and the process
2. **Form 1** - Collect basic information
3. **Form 2** - Collect additional details (triggered automatically after Form 1 is completed)
4. **Review email** - Send summary for contact confirmation
5. **Final form** - Collect signature or final approval
Use automations to trigger subsequent steps based on completion of previous steps.
[Learn more about Automations →](/cases/automations)
### Conditional Steps
Steps that appear or execute based on case data:
* Show "Additional documentation" form only if contact selects certain options
* Send different emails based on contact language preference
* Trigger specific workflows based on collected data
Configure conditional logic in case templates using automations and step visibility rules.
## Common Configurations
### Email Step Configuration
* **Sender** - Select from connected mailboxes
* **Recipients** - Main contact, secondary contacts, or custom addresses
* **Subject** - Plain text or with variable injection
* **Body** - Rich text editor with variable support
* **Attachments** - Upload files to include
* **Form link** - Optional form to include
### SMS Step Configuration
* **Recipient** - Contact phone number
* **Message** - Text content (160 character limit recommended)
* **Sender** - Workspace SMS number
### Form Step Configuration
* **Form template** - Select from existing templates or create quick form
* **Recipients** - Which contacts receive the form
* **Delivery method** - Email or SMS
* **Deadline** - Optional expiration
* **Reminders** - Automatic follow-up configuration
### Internal Form Configuration
* **Form structure** - Fields for team to complete
* **Assigned to** - Which team member completes it
* **Required fields** - Mandatory data points
* **Visibility** - Who can see the completed form
***
## Next Steps
Design predefined steps in templates
Learn about form design
Automate step execution
Understand case fields
# Product updates January, 2026
Source: https://docs.penbox.io/changelog/2026-01
## Penbox Connect API 2.0: Connect Anything, Effortlessly
We're excited to announce Penbox Connect API 2.0: a complete reimagining of our integration platform that makes connecting Penbox to your tools, workflows, and systems simpler than ever.
Whether you're building a CRM integration, automating workflows, or creating custom dashboards, Connect API 2.0 gives you the flexibility and power to connect anything to Penbox with minimal friction.
## What's New
### 🔐 Simple Token Authentication
**Getting started is now instant.**
We've replaced complex OAuth flows with simple Bearer token authentication. Create a token in your dashboard, copy it, and start making API calls immediately—no redirect URIs, client secrets, or authorization codes required.
Go to Settings → API Tokens, create a token, and you're ready to build.
**Security you can trust:**
* Instant revocation from the dashboard
* Usage tracking and audit trails
* Workspace-scoped permissions
* Secure token storage with industry-standard encryption
[Learn about Authentication →](https://docs.penbox.io/api-reference/authentication)
***
### 🏢 Multi-Workspace Support
**One integration, unlimited workspaces.**
Organizations managing multiple Penbox workspaces can now use a single API token across all of them. No more juggling separate integrations for different subsidiaries, brands, or client environments.
New `/v1/workspaces` endpoint lets you discover available workspaces and filter resources by workspace for consolidated reporting and cross-workspace automation.
Perfect for:
* Multi-tenant applications
* Agency workflows managing multiple clients
* Organizations with multiple brands or subsidiaries
* Consolidated analytics and reporting
[View Workspaces API →](https://docs.penbox.io/api-reference/workspaces/list-workspaces)
***
### 📊 Complete Case Management API
**Build sophisticated workflows programmatically.**
We've expanded case management with comprehensive endpoints for the entire case lifecycle:
* **Create cases** with templates, contacts, and prefilled data
* **Update case data** with batch operations and full attribution tracking
* **Manage contacts** on existing cases
* **Start case steps** to progress workflows programmatically
* **Count and filter cases** for dashboards and reporting
* **Discover case templates** to understand available workflows
Everything you need to integrate Penbox cases into your existing systems and processes.
[Explore Case APIs →](https://docs.penbox.io/api-reference/cases/list-cases)
***
### 🔍 Two Levels of Filtering
**Simple when you need it, powerful when you want it.**
We've introduced a dual-tier filtering system that scales with your needs:
**Method 1: Direct Query Parameters**
Clean, readable URL parameters for everyday filtering:
```bash theme={null}
GET /v1/cases?workspace[id]=abc-123&owner[email]=john@acme.com&status=pending
```
Perfect for straightforward queries—simple to construct, easy to read, and covers most common use cases.
**Method 2: Advanced JSON Filter**
When you need complex queries, use the `filter` parameter with operators:
```bash theme={null}
GET /v1/cases?filter={"attributes":{"created_at":{"$gt":"2024-01-01"}}}
```
Build sophisticated queries with:
* **Comparison operators:** `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`
* **Attribute filtering:** Filter on any case field
* **Relationship filtering:** Query across related entities
* **Combined conditions:** Mix multiple criteria in a single query
Go as deep as you need without custom backend logic.
[Learn about Advanced Filters →](https://docs.penbox.io/api-reference/advanced-filters)
***
### 📈 Enhanced Forms & Templates
**More control over form workflows.**
Expanded capabilities for form management:
* **Count endpoint** with grouping by status, template, or custom fields
* **Advanced filtering** on forms with date ranges and complex queries
* **Template discovery** to list available forms and understand their structure
* **Template configuration** access to get options and field definitions
Build form creation flows directly into your applications with full template awareness.
[View Forms API →](https://docs.penbox.io/api-reference/requests/list-requests)\
[View Templates API →](https://docs.penbox.io/api-reference/flows/list-flows)
***
### 📎 Better Attachment Management
**Simplified file operations.**
* Download multiple attachments merged into a single PDF
* Upload files with metadata for better organization
* Access signatures and signed documents programmatically
[Attachments API →](https://docs.penbox.io/api-reference/attachments/get-attachment)
***
## Why This Matters
**For Developers:**
* Get started in minutes instead of hours
* Simpler authentication with no OAuth complexity
* Consistent patterns across all endpoints
* Complete API documentation with interactive examples
**For Integration Teams:**
* One token for all your workspaces
* Build once, deploy everywhere
* Two-tier filtering eliminates custom query logic
* Clear rate limits and error handling
**For Product Teams:**
* Connect Penbox to any tool in your stack
* Build custom dashboards and analytics
* Automate case creation from any source
* Create embedded workflows in your applications
***
## Getting Started
1. **Create an API token** at [app.penbox.io/settings/api](https://app.penbox.io/settings/api)
2. **Make your first request:**
```bash theme={null}
curl https://connect.penbox.io/v1/workspaces \
-H "Authorization: Bearer pnbx_your_token_here"
```
3. **Explore the full API** at [docs.penbox.io/api-reference](https://docs.penbox.io/api-reference/introduction)
***
## Documentation & Resources
* **Complete API Reference:** [docs.penbox.io/api-reference](https://docs.penbox.io/api-reference/introduction)
* **Authentication Guide:** [docs.penbox.io/api-reference/authentication](https://docs.penbox.io/api-reference/authentication)
* **All API Endpoints:** [docs.penbox.io/api-reference/all-endpoints](https://docs.penbox.io/api-reference/all-endpoints)
* **Advanced Filtering:** [docs.penbox.io/api-reference/advanced-filters](https://docs.penbox.io/api-reference/advanced-filters)
* **Pagination & Sorting:** [docs.penbox.io/api-reference/pagination-and-sorting](https://docs.penbox.io/api-reference/pagination-and-sorting)
***
## Backward Compatibility & Migration
**All existing integrations continue to work without changes.**
This release is fully backward compatible—your current API integrations will continue to function exactly as they do today.
**For new integrations or future-proofing existing ones:**
We recommend exploring the new endpoints and Bearer token authentication. The 2.0 endpoints provide better consistency, more powerful filtering, and expanded capabilities that will serve as the foundation for future API development.
Check out the [new endpoints](https://docs.penbox.io/api-reference/all-endpoints) to see what's possible.
***
## Feedback
We built Connect API 2.0 to make integration simple, powerful, and flexible. If you have suggestions or questions, we'd love to hear from you.
Contact your Customer Success Manager or email us at [product@penbox.io](mailto:product@penbox.io).
This update is available to all customers immediately.
**— The Penbox Team**
# Product updates February, 2026
Source: https://docs.penbox.io/changelog/2026-02
# Granular Data Retention & Deletion Audit Log
**Data governance has always been at the center of Penbox.** We've taken a major step forward with two new capabilities.
## Granular Data Retention & Lifecycle Settings
You can now define **granular data retention and lifecycle settings** for both cases and forms, including cascade options for associated forms and contacts. For example, delete cases after 90 days of inactivity, remove closed cases 30 days after closure, or set a hard cap regardless of status.
## Data Deletion Audit Log
Administrators get a **detailed data deletion audit log** that shows exactly what was deleted from Penbox, when, and which rule triggered it—so you can demonstrate compliance and trace any data removal.
[Data Security documentation →](/workspace/settings/data-security)
# Document Intelligence API: Extract Data from Any Document
Penbox Document Intelligence is now available directly from Connect API. Extract structured data from invoices, ID cards, contracts, and forms—all in one API call. Built for real-time workflows, it delivers frontier results with fast processing tailored to the administrative documents your users deal with every day.
## What's New
### Direct API Access
**Document Intelligence is now in your API.**
Send document images or PDFs to `POST /document_intelligence`, define the fields you want to extract, and get back structured JSON—no separate setup or integration required. Perfect for automating invoice processing, onboarding flows, compliance checks, or any workflow that starts with a document.
The engine is fast, built for real-time use, and tuned for the kinds of documents Penbox users typically process: administrative forms, invoices, bank statements, ID documents, and contracts.
[Create Document Intelligence →](/api-reference/document-intelligence/create-document-intelligence)
### List Field Type
**Extract repeating items—invoice lines, table rows, or complex lists.**
You can now define fields with `type: "list"` to extract structured arrays from documents. Each item in the list can have its own sub-fields (product, quantity, price, etc.), so you get fully typed invoice line items, table rows, or any repeating structure—not just flat text.
Use `fields` to define the structure of each item, and the API returns an array of objects with extracted values per item. Empty lists return `[]`; missing regular fields return `null`.
### Validation & Confidence
**Know how reliable each extraction is.**
Every extracted field now includes a `confidence` level (`high`, `medium`, or `low`), so you can decide when to auto-approve or flag for review. The response also includes a `validation` object with:
* **`is_valid`** – Whether the document passes your validation rules
* **`user_explanation`** – A human-readable explanation in your chosen language
* **`is_blurry`** – Whether image quality may affect readability
Use this to route low-confidence extractions or blurry documents to human review automatically.
[Learn more in the API Reference →](/api-reference/document-intelligence/create-document-intelligence)
# Product updates March, 2026
Source: https://docs.penbox.io/changelog/2026-03
# Store and reuse files across your workspace
Penbox now includes a dedicated file storage area under **Console > Files**. Upload documents, images, and assets once, then reference them wherever you need them: in forms, cases, or templates. The new workspace file picker shows authenticated image previews inline, so you see what you are selecting before you select it.
### Improvements and fixes
* **Forms.** Italian and Portuguese are now available as form languages. The translation system has been rebuilt around a cleaner structure, making future language additions faster.
* **Case inbox.** Change status and "waiting for" directly from the datatable, without opening the full case view.
* **Document Intelligence.** Drag and drop to reorder fields in the template editor.
* **Analytics.** Filter reports by a specific form to see only the data that matters for that process.
* **Cases.** Cancelled cases now appear in the All tab, making them searchable without switching views.
# Publish, version, and revert your case templates
**Case templates** now support versioning. Publish a template to lock its current configuration as an immutable snapshot, complete with a version number. Revert to any previous version if something needs to change back. Every case created from a template records which version it used.
### Publishing
When a template is ready, publish it. Penbox creates a numbered snapshot of the full template configuration: fields, structure, rules. The template stays editable for future changes, but the published version is frozen and available for use across the platform.
### Reverting
Roll back a template to any previously published version. The revert restores the full configuration from that snapshot. Useful when a change turns out to be wrong, or when a team needs to return to a known good state quickly.
### Version tracking on cases
Every case created from a published template records the version number it was built from. For templates without a published version, existing behavior is unchanged. No migration, no disruption.
### API
Integrations that create or update cases through the API automatically use the latest published version. No code changes required on the caller side.
[Docs →](/docs/template-versioning)
# Document Intelligence templates
**Document Intelligence now has a template system.** You can define reusable templates that combine **data extraction rules and document validation rules** in one place — then reference them from the API or directly inside a case, without redefining anything each time.
## Template editor
The new visual **template editor** lets you build, test, and trust your document rules without writing any code. Define extraction fields and validation conditions, run them against real documents to see results instantly, then save the template. Once saved, you can **copy its JSON** for direct API use or reference it by ID anywhere in Penbox. For example, build a template for expense reports once — with required fields and validation rules — and reuse it across every integration and case flow that processes them.
## Templates in Connect
**CONNECT's `POST /document-intelligence` endpoint now accepts a `template` reference**, so integrations no longer need to send full rule definitions on every call. For example, pass a template ID instead of an inline configuration and let Penbox resolve the rules server-side — cleaner payloads, easier maintenance.
## Live results in cases
On any **file element** inside a case, you can now attach a **Document Intelligence template**, map its output fields to case data, and see the results appear live as soon as a document is uploaded. For example, attach a contract template to a file element, map extracted clauses and validation outcomes to case attributes, and have everything populated automatically the moment an agent uploads the document — no manual entry required.
# UX, forms, and intelligence improvements
**A round of improvements across cases, forms, and document intelligence** — from a new case status to smarter forms and real-time sync.
## Cancelled status for cases
Cases that are stopped but not resolved now have a dedicated **Cancelled** status, separate from **Closed**. Track cancelled cases in your analytics, filter them independently, and get a clearer picture of where work stops and why. For example, distinguish between cases closed after resolution and cases abandoned mid-process without mixing them in your reporting.
## Revamped Ask with Form
**Ask with Form** has been redesigned to be more precise and configurable. You can now define **predefined values** directly in the settings of a case or case template, giving your contacts guided options rather than open-ended fields. You can also toggle whether to **include the existing value** in the form sent — useful when you want contacts to confirm current information or start fresh. For example, pre-fill an address field with the value already on file and let the contact confirm or update it.
## Skip welcome page on forms
**Smart Checklists** now support a **Skip Welcome Page** option. Enable it on any form where you want contacts to land directly on the first question — no introductory screen, no friction. For example, use it for short, targeted data collection requests where the context is already clear from the email or message that accompanied the link.
## Whitelabel links for generic forms
Generic forms now fully support **whitelabel domains**. Copy the whitelabelled link directly from the app — no need to use `flow.penbox.io` anymore. For example, share a branded form link with contacts without any Penbox domain appearing in the URL.
### Improvements & fixes
* **Email Intelligence.** Improved document mapping accuracy and a cleaner UX for reviewing extraction results.
* **Live case sync.** Case pages now update in real time — every event is pushed live, and you can see when a teammate is viewing the same case alongside you.
* **Performance.** Multiple enhancements across the platform for faster load and smoother interactions.
# Analytics
**Admins now have a dedicated Analytics page** to monitor case performance, agent productivity, and form engagement across the workspace—filtered by date range, owner, or flow.
## Overview
The **Overview tab** gives you an at-a-glance summary of your workspace: active cases, closed cases, average resolution time, and average satisfaction rating. Two charts—**daily case activity** and **case age distribution**—let you spot trends and backlogs before they become problems. For example, use the daily case activity chart to identify spikes in incoming requests and correlate them with team capacity.
## Cases
The **Cases tab** breaks down resolution performance by flow: average resolution time, median resolution time, and resolution rate per template. For example, pinpoint which flows consistently take the longest to close and prioritize process improvements there.
## Owners
The **Owners tab** surfaces per-agent performance so managers can see who is handling the most cases and how quickly. For example, compare resolution times across your team to identify where coaching or workload balancing is needed.
## Forms
The **Forms tab** tracks **Smart Checklist** engagement: forms created, forms completed, completion rate, and average completion time—broken down per form. For example, identify which checklists have low completion rates and adjust them to remove friction.
# Product updates April, 2026
Source: https://docs.penbox.io/changelog/2026-04
# AI-powered email templates
Email templates can now act as AI prompts for automated emails and draft replies. Define the strict canvas once — structure, tone, required sections, variables — and Penbox uses the case context and previous emails to generate messages that stay compliant with the template while still reading as specific and human.
### Template as prompt
The template becomes the prompt. Use it to control what every automatic email or suggested reply must include, while letting AI adapt the wording to the recipient, the case, and the conversation so far.
### Automatic, but personal
Workflows can send AI-generated emails automatically, or provide them as draft replies for review. Teams keep the consistency of a fixed email template without losing the human tone that makes a message feel written for the case in front of them.
### Case links in emails and SMS
Email and SMS messages sent from workflows can now automatically include a direct link to the related **Case**. Recipients and teammates can jump back to the right case without searching, keeping the full conversation connected to the work it belongs to.
# Penbox Agent is now available in beta
**Penbox Agent** is now available in beta for every workspace. It gives teams an AI assistant inside Penbox that can help create, adapt, explain, and refine complex configuration work across forms, logic, signatures, variables, and Document Intelligence.
### Build and edit faster
The agent can create new forms, adapt existing ones, and make precise surgical changes without forcing you to rebuild a full flow. Ask it to change copy, restructure steps, translate content, manage variables, configure signatures, or apply advanced **Penscript** logic.
### Understand what to change
Penbox Agent can also explain how a form or configuration works, what a setting does, and what should change before you apply it. Use it to reason through complex logic, review form behavior, or understand how variables and conditions connect.
### Undo and redo agent actions
Every action performed by the agent can be undone and redone. Try a change, inspect the result, and roll it back if it is not what you wanted.
### Research version for Document Intelligence Templates
We've also added Penbox Agent in a research version for **Document Intelligence Templates**. It can help shape extraction fields, validation logic, and template configuration for document-heavy workflows.
# Install ready-made case templates from the marketplace
Workspace admins can now browse a Penbox-curated **Marketplace** of ready-made **case templates** and install copies into their own workspace. Open **Browse Marketplace** from the case templates page, filter by categories like **Broker**, **Insurance**, or **HR**, and start from a complete operational blueprint instead of a blank template.
### Complete case templates
Marketplace templates can include the pieces that make a case work end to end: pre-made forms, step sequences, case data, automations, and Document Intelligence configuration. Installing a template also installs the forms referenced by its steps, so the copied case template is ready to adapt inside your workspace.
### Independent workspace copies
Installed templates belong to your workspace. Teams can edit their copy without changing the original marketplace template, while Penbox keeps a reference to the source template for future marketplace features.
# Any form can now become a case
Forms now fit directly into the Penbox case model. When **Forms as cases** is enabled, every sent form can create or attach to a **Case**, with the timeline, contacts, workflow steps, and automations that teams use to manage work from start to finish.
### Forms as cases
Forms shared from campaigns, integrations, or direct links no longer have to live outside the operational workflow. Penbox can wrap each form in a case, apply the right case template when one is configured, and keep contact details in sync when the recipient updates their information.
### Case references
Cases now display a short **reference** in the case list and case header. Click the reference to copy it, share it with teammates, or use it when connecting external conversations back to the right case.
### Email mapping
Email workflows can use case references to keep messages attached to the correct case. Replies and forwarded conversations are easier to route, so context stays with the case instead of getting lost in a separate inbox thread.
# A new Penbox brand and redesigned app experience
Penbox has a new visual identity across the product. The redesigned app introduces our updated color system, including our signature **Zen** color, refreshed buttons, cleaner surfaces, and a more consistent interface from first login to daily workspace management.
### Sign-up and sign-in
The new **sign-up** and **sign-in** screens bring the updated brand forward while keeping authentication focused on the next action. New teammates get a calmer, more direct path into the workspace.
### Workspace menu
We've refreshed the **Workspace menu** with a cleaner structure and stronger hierarchy across navigation, actions, and settings. Switch context, find workspace settings, and manage daily work with fewer visual distractions.
### Filters and controls
We've added new filters across the app and updated the color and button styles that power common actions. Filters are easier to scan, buttons feel more consistent, and the updated palette makes important states clearer.
# Product updates May, 2026
Source: https://docs.penbox.io/changelog/2026-05
# Embed forms securely in your website
You can now embed **forms** directly into your public website, customer zone, or application using an `
# AI-drafted reminders for your case steps
When a step is waiting on a contact, you can now add follow-up **reminders** in a click. Penbox drafts the subject and body from the case context, you review and edit, and each reminder goes out on the schedule you set.
## Draft with AI, send on your schedule
Open the reminders on any pending form step, generate a draft, and adjust the wording before it sends. Set the delay in **days** or **weeks** and edit the schedule at any time directly from the step.
## Reminders that know when to stop
Reminders on a form that's already completed or archived are automatically marked **Ignored** and skipped, so a contact never gets chased for something they've already done.
## Or just ask the agent
On a case that's already in progress, ask **Penbox Agent** to add a reminder on a sent step and it schedules it for you.
# Penbox Agent is now generally available
**Penbox Agent** is out of beta and available to every member of every workspace. Beyond building and adapting **forms**, the agent now builds and manages your **cases** end to end. Describe what you want in plain language and it configures the case template for you.
## Build and run cases
The agent creates and edits case templates: it adds steps, wires up **custom statuses** with automatic transitions, sets **conditional execution** so steps run only when they should, and configures **automations** that fire when a step is sent.
## Connect forms to case data
Ask the agent to mirror a form's fields into your **case data** and it maps each field into the right section automatically, with no need to wait for a contact to complete the form first. It can also reuse the forms you already have instead of rebuilding them inline.
## Configure Document Intelligence
The agent can set up **Document Intelligence** on a file field, creating the matching data fields and mapping extracted values into your case.
## Pick up where you left off
Every conversation is saved with an **AI-generated title** and grouped by what you were editing, so you can reopen a past chat and continue. As before, every action the agent takes can be undone and redone.
# Product updates June, 2026
Source: https://docs.penbox.io/changelog/2026-06
# Draft cases get their own tab
Cases you're still preparing now live in a dedicated **Drafts** tab, separate from the work in progress. Your case list shows what's actually running, and nothing half-configured slips into the day-to-day view.
### Improvements and fixes
* **Dates.** Case data dates now display as DD/MM/YYYY everywhere.
* **Outlook.** The add-in only lists steps you can actually act on, and reply drafts refresh correctly after edits.
# A new Outlook add-in, built around your cases
The Penbox add-in for Outlook has a new experience designed around your **cases**, so you can run the work where it starts, right next to the email.
## See and create cases from any email
Open an email and Penbox shows the matching **case**, or lets the **agent** create one from the message and sync the sender into your contacts. Add an incoming email to a case you already have in a click.
## Fill your case data, including documents
Read and edit the case's data fields straight from the add-in, upload the files a step needs, and keep everything moving without switching windows.
## Ask and draft in place
Ask for anything with an **AI-drafted form request**, and get reply drafts written in the customer's language, all without leaving your inbox.
# Improvements and fixes
* **Signatures.** Documents sent for signature without a placement keep their original filename.
* **Forms.** Text pasted with typographic dashes no longer trips validation. We normalize them automatically.
* **Company lookups.** French SIREN verification now runs on INSEE's new Sirene API.
* **Security.** Resolved 9 critical dependency vulnerabilities across the platform.
# Send for signature from the Outlook compose window
While writing an email, attach a Penbox action to it: send a document for **signature** or **share documents** securely, without leaving Outlook or breaking your flow. The recipient gets the polished Penbox experience; you never left your draft.
# Introducing Penbox Connections
Penbox now connects **natively and deeply** to the systems you already work in. A **Connection** binds a case to a live record in another system, so the data you collect flows straight to and from your source of truth. No exports, no copy-paste, no brittle sync to maintain in between.
## A true two-way link
Fields on a case are **bound** to the connected record: they read the latest values from the source and write your changes back. Edit a detail in Penbox and it lands in the connected system, and the other way around. It's a live link, not a one-time import.
## Live today with BRIO
The first connector is **BRIO**, and it's live for three kinds of records with full two-way sync:
* **Contacts.** Bind a case to a BRIO party and the client's identity (name, phone, address) stays in step on both sides.
* **Risk objects.** Connect the vehicles, properties, and other insured objects on the case.
* **Claims.** Create and keep claims (sinistres) in sync as the case progresses.
More connectors and connected objects are on the way.
# Contact details at hand on every case
Click any contact on a case to open a **side panel** with their full details, and edit them inline, right there. No detour through the contacts page to fix a typo in a phone number.
### Improvements and fixes
* **New case.** Phone numbers are validated as you type when creating a case.
# A richer chat with Penbox Agent
Bring more into your conversation with **Penbox Agent**. Paste images and content chips straight into the chat and attach files, so the agent works with the full picture. The message box now grows and scrolls with longer input, so nothing gets cut off.
# Emails find their own case
Mark any case field (a policy number, a claim ID, a reference from another system) as a **reference**. References show as chips in the case header, and when an email arrives mentioning one of them, Penbox attaches it to the right case automatically, even a brand-new thread from a sender you've never seen.
Penbox reads the identifiers out of the email and only attaches on an exact, unambiguous match. When in doubt, it creates a new case rather than guessing.
# See and manage every message sent on a request
Click any email or SMS on a request to see the full message exactly as your contact receives it: subject, sender, recipients, delivery status, and the rendered body. For messages that haven't gone out yet, act on the spot: **pause**, **resume**, **send now**, or **delete**.
### Improvements and fixes
* **Cases.** Inspect a case's data as structured JSON from the data panel.
* **Forms.** A step with an API button now validates only the fields before it.
* **Documents.** Multiline text no longer disappears from generated PDF documents.
# API elements that react to changing answers
An **API element** can now watch other answers in the form. When a watched answer changes, the previous API response is marked stale and the call must re-run before the step can be submitted, so a decision based on the old answer never slips through.
# Product updates July, 2026
Source: https://docs.penbox.io/changelog/2026-07
# Introducing Tasks
Knowing a case is waiting on something is not the same as knowing **what you have to do about it**. **Tasks** turn a case into a short list of next actions, with an owner and a due date.
The case header shows how many tasks are still to complete. Open it and you get **Your tasks**: what to do, who it's assigned to, and when it's due. Tick one off and the case timeline records it.
Every task sits in exactly one court, because it belongs to whoever can actually close it:
* **Your client's court** — "Complete the intake form", due in a week. It flips to *in progress* the moment they open the form, and closes by itself when they submit.
* **Your team's court** — "Review the submitted answers", due two days after the form comes back.
You don't set any of this up case by case. Tasks come from your **case template**: each form step creates the right tasks when the form is sent, opened, or completed. Per step you can switch tasks off, change a due date, assign one to a specific colleague or to a contact role, or add tasks of your own, like *Call the insurer, due in 3 days*.
**Nothing goes quiet.** When a client task runs past its due date, Penbox raises a follow-up task for the case owner, due the next day, linked back to what was missed. Close either one and the other closes with it. And if the case is connected to a **BRIO** claim, that follow-up also logs an open activity on the claim folder, so a broker working in BRIO sees the same thing you do. Routine tasks are never mirrored — BRIO only hears about it when someone genuinely has to act.
Tasks are available today in all workspaces.
# Case collaborators
A case used to know only one member of your team: its owner. Everyone else working on it was invisible. **Collaborators** fix that.
Open a case and you'll see the people involved next to the owner: hover an avatar for a name, click **+** to add a colleague, remove anyone who no longer needs to follow along.
Most of the time you won't have to add anyone. Penbox recognizes your workspace members and enrolls them by itself:
* A colleague emails a case, is CC'd, or replies from a connected inbox — they become a collaborator
* A colleague forwards a client email to Penbox — the client is attached as the contact, and the colleague as a collaborator
* A case template can carry default collaborators, copied onto every case created from it
Each avatar tells you where it came from, so you can tell an automatic enrollment from a manual one.
**Colleagues are no longer mistaken for clients.** Before, a member who emailed a case could be attached as a contact, sometimes in the main client slot. Recognized workspace addresses are now recorded as collaborators instead, keeping your client data clean.
**Find your work faster.** The cases list has a new **My cases** filter that brings back everything you own *or* collaborate on, with matching counts.
Collaborators are visibility only: they don't change who can access a case. They're available today in all workspaces.
# AI generated fields
AI generated fields are a new way for Penbox to keep your case data up to date for you.
Choose the fields the AI should maintain, describe what each one should contain in plain language, and pick the event that triggers it, such as an intake form being completed. From there, Penbox uses everything already on the case, including forms, contacts, and existing data, to write a structured value into each field.
For example:
* Summarize a claim as soon as the intake form is completed
* Classify new cases into a category and route them to the matching case template
* Derive a risk level or priority from the answers a client submitted
Generated values are always labeled. Each one carries an AI badge in the case data panel, so anyone working on the case can tell which values were written by the AI and which were entered by a person. Editing a field replaces the generated value with yours, and the field is treated as manual from then on.
AI generated fields are configured on case templates through the case template assistant: describe the job and it sets up the trigger, the target fields, and the instructions for you. They are available today in all workspaces. Learn more in the [docs](/cases/ai-generated-fields).
# Configure your New Case form
Choose exactly which fields the **New Case** form asks for, and whether each one is **required**, **optional**, or **hidden**. Set it once for the whole workspace, or override it per case template so every kind of case starts with just the fields it needs. Required fields are enforced, so a case can't be created without them.
# Secure generic links with one-time passcodes
Forms shared through a **generic link** start anonymous, so anyone with the link could open them. Now, the moment a filler enters their email or phone, Penbox locks the request to that person: reopening it later requires a **one-time passcode** sent to that same email or number. The person currently filling it in is never interrupted, and the protection can only tighten, never loosen.
# Improvements and fixes
* **Signatures.** Requests now complete reliably when the signer is redirected back, and transient hiccups while a signature is being prepared no longer block the page.
* **Document Intelligence.** Extraction results show the correct field name and type again.
* **Sessions.** An expired session recovers silently in the background instead of failing your next action.
* **Performance.** Document generation now runs off the main thread, keeping the app responsive under load.
* **Security.** Patched high-severity vulnerabilities in axios and multer.
# Connections keeps growing
Since launching **Penbox Connections**, we've deepened the BRIO connector for contacts.
## Import at scale
Bring in your BRIO contacts in the background with live progress, including contacts that have no email or phone.
## Add contacts back to BRIO
Add a case contact to BRIO as a party without leaving Penbox, straight from their hover card.
## Readable everywhere
Party codes now display as human-readable labels, BRIO users are matched to your Penbox workspace members, and connected data appears the moment you open a case, with no refresh needed.
# Your connected mailbox turns email into cases
Connect an **Outlook** or **Gmail** mailbox and Penbox reads incoming email as it arrives, matches it against your case templates, and opens the right **case** automatically. The case is created in the language the email was written in, with the message and its attachments already attached.
The add-in becomes a reader: open a classified email and the case is already there, no waiting. You stay in control with a per-template switch that decides which templates take part.
# Product updates August, 2026
Source: https://docs.penbox.io/changelog/2026-08
# Claim intervenors, filed in BRIO
A claim rarely involves the policyholder alone: there is the adverse insurer, a repairer, sometimes another company on the policy. You can now name them **from Penbox**, without opening the claim in BRIO.
An intervenor is a case contact with a **role**. On the contact's card, record the role, a note, and the third party's own claim and policy references. When you're ready, push it to BRIO from the contact card's **BRIO sync** chip, which counts what is still to send. Once filed, the role is locked, since BRIO does not allow an intervenor to be edited or removed afterwards.
It works both ways: the insurer BRIO files on its own when a claim is created is mirrored back onto the case, in the slot your case template declares for that role. Intervenors are also available in the Outlook add-in.
# Improvements and fixes
* **Currency fields** accept a placeholder and a default value.
* **File sizes** are shown in the form's language (Mo and ko in French).
* **AI failures are visible.** When an AI provider fails, the error now surfaces instead of the work being silently dropped.
* **Downloads.** Files from unnamed form fields get a default archive name.
* **Email.** Emails sent by Penbox itself are linked to their case as soon as they are received.
# A cases inbox you can shape
The cases inbox now adapts to each kind of case, instead of showing the same columns for everything.
## Columns per case template
Filter the inbox on a case template and click **Columns** to pick, reorder, and drag in the fields that matter for it, from the base columns to any field of its data. Data columns show each case's current value and can be sorted. The same setting is available in the case template's settings, with a reset to default.
## Filters on case data
A new **Data** filter lets you set criteria on the template's fields, with operators that match the field type: *contains*, *is* or *is not* for text, comparisons for numbers, *on*, *before* or *after* for dates, the option list for choices, yes or no for checkboxes. Criteria combine, chips let you edit them in place, tab counts follow, and the whole filter lives in the URL so you can share it. The search bar also matches a case's current data values.
## Export what you see
Export the inbox to **CSV or XLSX** with your current filters and every data value. You get one file per case template, zipped together when there are several.
## Know what moved
A **real-time unread indicator** flags cases with activity you haven't seen, each case shows its **time in status**, and a live chip announces new cases as they arrive.
# Connect a Microsoft 365 shared mailbox
Workspace admins can now connect a **Microsoft 365 shared mailbox**, such as a claims address, at workspace level. Type the shared address, then sign in as a colleague who holds Full Access and Send As on it. Penbox reads and sends as the shared address, never as the person who connected it.
A shared address is recognized as internal: it is never attached to a case as a client contact, and it never receives an automatic draft. The case history says which mailbox a case came from, for example *Case created from an email received on* `claims@yourcompany.com`.
# More from your automations
* **Write API responses into case fields.** An API Call field can now map its response onto case data fields, so a lookup in a third-party system fills the case by itself.
* **Schedule a BRIO activity.** A new automation creates a dated to-do on the case's BRIO party or claim folder, a set delay after its trigger, so an open file is not forgotten when a client never replies.
* **A visual editor for AI generated fields.** Set up AI generated fields without writing configuration, and see at a glance which fields an automation maintains.
# Improvements and fixes
* **New case.** The contact field suggests the five contacts you most recently added to your cases.
* **Language.** A new case takes the language of its case template, and built-in status labels are translated in the API.
* **Forms.** Form status badges show how many fields are filled out of the total.
* **Partners** can provision a complete workspace in a single API call.
* **Email.** The main contact of a case created from an email is decided from the email's sender and recipients, and an automated mailbox can never become the main contact. Images pasted in an email body are extracted, and email summaries are written in the case's language.
* **BRIO.** Emails are filed as `.msg`, so BRIO displays them correctly, and claim guarantees use their official labels.
* **Case header** no longer overlaps and collapses progressively on smaller screens.
* **Forms.** Upload errors are shown in the form's language.
# Improvements and fixes
* **AI generated fields** shimmer while the AI is writing them, and keep their AI badge afterwards.
* **Outlook.** An email sent from Outlook lands on its case and closes the matching step.
* **Signatures.** Requests with several signers no longer send duplicate follow-up requests.
* **Email classification** takes attachment file names into account. When a mailbox is first connected, emails older than 30 days are no longer turned into cases.
* **Contacts.** An edit in progress is no longer lost when the contact list refreshes.
* **API.** Case statuses come with their label, and contacts can be searched with `contact[search]`.
* **Document Intelligence.** The button is hidden in workspaces that don't have the feature.
# An agent at your side in Outlook
The Outlook add-in now carries a **persistent agent**. It stays with you as you move from one email to the next, works quietly in the background, and keeps its conversation inside the pane.
* **It drafts for you.** Ask for a new email or a reply, and the draft lands as the case's next step. The agent knows which Penbox draft you have open.
* **It never creates a case behind your back.** Cases are created only when you ask for one, and Penbox checks for an existing case first.
* **Add an email to an existing case** when Penbox classified it but you know where it belongs.
* **See and change the case owner** straight from the case header.
The case panel is tighter too: data sections follow the order and the *Collapsed by default* setting of the web app, and contacts known in BRIO carry the BRIO icon.
# BRIO: emails and signed documents reach the claim
* **Sent emails** are synced to the BRIO claim folder, not only the received ones.
* **Signed documents** are pushed to BRIO, and the standard claim documents now include the VAT declaration, invoices, quotes, communications, and certificates.
* **Guarantees** show their deductible, and the AI can read the claim's cover.
* **The contract and the vehicle** stay consistent in both directions: pick one and the other follows.
* **The claim manager** is prefilled on cases created from a template.
* **Default sync mode.** Choose on the case template how its cases sync with BRIO.
# New automation steps, and every mailbox in one place
## Two new building blocks for case templates
* **Internal email step.** Notify the case owner or its collaborators by email. It is sent from Penbox, and a reply comes back into the case.
* **Update field action.** An automation can write a value into a case field directly, with no AI involved. For example, prefix a reference number when a status changes.
## Manage every connected mailbox
**Settings → Email** now lists every mailbox connected to the workspace: shared mailboxes, addresses on your authenticated domain, and your teammates' personal mailboxes. Each one shows its connection status, when it was connected, and which case templates create cases from it. Admins can disconnect any of them, and choose from a side panel which case templates a personal mailbox feeds.
## Document Intelligence per field
In AI Extract, each upload field can now use its own Document Intelligence model, Fast or Advanced.
# Improvements and fixes
* **API.** Case statuses are exposed on the case routes, and case template statuses in the public API.
* **Assistant.** It only changes a case's status when you ask it to.
* **Contacts.** Penbox's own mailbox is never attached as a case contact, and creating a case through the API updates an existing contact instead of duplicating it.
* **Incoming email** is matched to cases the same way in the add-in and on the server, and a document the case already holds is not classified twice.
* **BRIO.** The broker claim reference is shown in the claim picker, and a connected section keeps its fields when its record is unlinked.
# Meet Document Intelligence 3
Document Intelligence now ships **two models** instead of one, so the level of extraction can match the difficulty of the document.
**Fast** replaces the previous model on every existing document type, at the same **3 credits per page**. It is 30% faster and 25% more accurate, and it applies automatically: nothing to reconfigure, no change to your credit usage.
**Advanced** is new, at **6 credits per page**. It is 50% more accurate and reasons over the whole document instead of reading it field by field, which is what messy handwriting, checkbox grids, and multi page context require. Despite that, it runs only 10% slower than the new Fast model, so it is still quicker than the model it replaces.
Where Advanced earns its keep:
* **European accident statements (constat amiable)**, where handwritten boxes, free text, and the collision sketch have to be read together
* **Complex handwritten documents**, such as medical certificates and filled-in paper forms
* **Documents that need inference**, where a field is not written anywhere as such and has to be derived from the rest of the page
**You decide which model runs on which document type.** The Fast upgrade is automatic and costs nothing extra. Advanced is opted into per document type, so the 6 credit rate only applies where you asked for it.
Fast is live today in all workspaces. Advanced is available per document type, from your workspace settings or through your Penbox contact. An **Expert** model, for the hardest documents, is in development and planned for September. Learn more in the [docs](/guides/document-intelligence-models).
# Product updates September, 2026
Source: https://docs.penbox.io/changelog/2026-09
# Introducing the customer portal
Your clients no longer have to dig through their inbox to know where their file stands. The **customer portal** gives each contact one stable link to their case.
From that link, a contact can:
* **Follow the case**: its status, written for them, and the steps still expected from them
* **Fill in forms** without leaving the portal, welcome and ending pages included
* **Send a document or a message** to the case owner without being asked, which appears in the case history
* **Download** the documents shared with them
The portal carries your workspace's logo and colors. Access can be protected with a **verification code** sent by email or SMS. The code is sent only when the contact clicks to request it, so email scanners and link previews no longer burn codes.
Turn the portal on in **Settings → Customer portal**, set the link validity and whether a code is required, then override any of it per case template. A new automation sends the portal link at the right moment of the case.
# Import a case template from another workspace
Working across several workspaces? The case templates list has a new **Import…** button that copies a case template from any other workspace you belong to. The email templates and Document Intelligence templates it uses come along, and Penbox prevents importing the same template twice into the same workspace.
# Inbox Triage and Outlook, one step further
## Inbox Triage
* **Reply in a click.** Draft a quick reply with the agent or from an email template, without leaving triage.
* **Your mailbox stays in sync.** An email archived directly in Gmail or Outlook leaves the triage queue too.
* **Archiving in the mailbox is now opt-in.** Handled emails stay in the shared inbox unless you turn on the new setting, so colleagues who don't use Penbox keep seeing them.
## Outlook: the case's next step
The add-in now proposes the **next step of the case**. When the agent's suggestion isn't right, a single searchable action center offers every alternative: the case's remaining steps, your email templates, and quick commands. Cancel brings the suggestion back. The read pane was also redesigned, with a clearer row of contacts and compact data sections.
# Know where every case came from
Every case and every change on it now records **who** made it, **how** (a person, the AI, an automation, a partner, or Penbox itself), and **through which channel** (the app, Outlook, the API, MCP, email, a form, or a connection). An email-born case can finally be told apart from one created by hand, in the API and in your automations. See [the case API reference](/api-reference/cases/get-case).
# A status-grouped cases view, in early access
A compact new view groups your cases by status, with display controls to choose what each row shows. It is in early access: ask your Penbox contact to enable it for your workspace. Each person can then switch between it and the classic table, and Penbox remembers their choice.
# Improvements and fixes
* **Penbox plugin.** Install it in two commands for Claude Code or Codex, from a public marketplace. See the [installation guide](/install).
* **Document Intelligence.** Create a new Document Intelligence template straight from a case template field.
* **Forms.** Invitations can be sent by SMS only. IBANs are validated against the official registry, and an incomplete IBAN is never saved.
* **Signatures.** The signer always gets back to the form after signing.
* **Translations.** Missing French and Dutch translations were filled in across the app.
* **BRIO.** A contact shows the broker office's own rank, an activity due on a weekend moves to the Monday, and the BRIO connection is checked daily and repairs itself.
* **Email.** A forwarded email is matched to a case only through its main contact, and attachments still being scanned by Microsoft Defender are no longer missed.
* **Automations** fire on data sent when a case is created.
* **Assistant.** The case template assistant can build multilingual forms.
# Feed a case through the API
The Connect API can now send **raw material to a case**: a call transcript, notes, a parsed email, and up to five documents. Penbox reads it the way it reads an incoming email, then either proposes the changes for review or writes them at once. Documents are filed on the right field, and values you send explicitly always win over the AI. Inputs create new fields by default when the case doesn't have them yet, with an option to turn that off. Every call, and every object it creates, now records which API token made it.
In Outlook, you can **send an email before the proposed step**, written from a template or by the AI.
## Fixes
* **Signatures.** A rejected signature no longer completes the request.
* **Domains.** A workspace domain can send only once every DNS record is in place.
* **Cases list.** A filtered tab count no longer leaks into the workspace-wide count.
* **Inbox Triage.** The subject and the reply area stay in view while you scroll a long email.
* **Branding.** Workspace logos fit the sidebar without being cropped.
# Initials on every page
Some contracts must be initialed on every page, not only signed at the end. A new **initials on every page** switch, next to the signature hints in the form editor, places a signing field on each page of the document. The signer applies them all in a single signing action.
## Email templates, scoped to case templates
Choose which case templates each **email template** appears in, and the order it appears in. Pickers now list only what fits the case at hand, instead of every template in the workspace. Nothing changes until you narrow a template down.
## Also in this release
* **Outlook.** Send a step's draft as a new email, with the step's own subject.
* **AI replies** know who they are writing as.
* **Template versions.** Open cases can be moved onto a newer version of their case template. Ask your Penbox contact.
* **Security.** Credentials are masked in logged URLs and API responses.
# Required fields and conditional visibility
Case data fields can now be **required**, and fields or whole sections can be **shown or hidden by a condition** on the rest of the case. Required fields carry a red asterisk, and so does a section that hides an unfilled one. Hiding a field never erases its value.
API Call fields wait for the required fields: their Run button stays disabled, with an explanation, until everything it needs is filled in. You can opt a field out of that check.
## Tasks and statuses over the API and MCP
Partners and AI agents can now read and manage **tasks** and **change a case's status** through the Connect API and the Penbox MCP server. A change made by an integration shows its name in the case history.
## Also in this release
* **Inbox Triage.** Attach files to a quick reply; they are filed on the case.
* **Outlook.** Apply an email template to the proposed reply.
* **Mailboxes.** Disconnecting a mailbox keeps its emails.
* **Email display.** Received emails are displayed in an isolated frame, so their styles never break the page.
# Introducing Inbox Triage
A shared mailbox receives everything: claims, questions, newsletters, spam. Until now, an email that matched no case template was either turned into a case or ignored. **Inbox Triage** gives those emails a queue of their own.
For each email, decide in one move:
* **Create a case** from the right case template
* **Attach it** to an existing case
* **Archive it**
A case is created only when you decide, with the whole conversation, its attachments, and the usual AI analysis. Spam never lands in your contacts. The queue can be searched, including on Cc and Bcc recipients, attachments open and download in place, and the **All emails** view says what became of each email.
Turn Inbox Triage on per mailbox, for a workspace mailbox or your own personal one.
# Drive Penbox from your AI agent
Penbox now has its own **MCP server**. Connect Claude, Codex, or any MCP client to your workspace and work on case templates, form templates, and cases from your agent, with your own permissions. Every change made over MCP is attributed to you in the case history. Read [how agents work with Penbox](/agents/introduction).
# Duplicate contacts, found and merged
Penbox now resolves contacts the same way everywhere: the same email address always leads to the same contact. When several contacts share it, Penbox picks one consistently and flags the choice for review, with one click to switch.
Existing duplicates can be **reviewed and merged** from the contacts page, and Penbox suggests a matching contact before you create a new one.
# Improvements and fixes
* **Search.** The cases inbox search is faster, ignores accents, and covers the title, the contacts, and the case template.
* **API Call fields** show an error message per field, and report the mappings that wrote nothing.
* **Automations.** Type `{` in an automation text field to insert a variable.
* **Archived cases** reopen when the client replies.
* **Email.** Inline images of received emails are displayed.
* **BRIO.** Open the claim, contract, or vehicle in BRIO straight from the case.
* **Assistant.** It knows how to configure API Call fields and workspace secrets.
# Product updates October, 2026
Source: https://docs.penbox.io/changelog/2026-10
# Document Intelligence Pro
Document Intelligence gets a third model, **Pro**, for the documents that still resist Advanced. This is the model we announced in August for the hardest documents.
Pro is built for dense handwriting and for fields that are blank or hard to read, where a weaker model is tempted to fill in a plausible value. It runs at **9 credits per page**, is processed in the EU, and is available on AI Extract upload fields and on Document Intelligence templates.
As with Advanced, you opt into Pro per document type, so the higher rate only applies where you choose it. Learn more in the [docs](/guides/document-intelligence-models).
# Outlook: context on every email, even without a case
Until now, an email with no case left the add-in mostly empty. It now shows **everything Penbox knows about the email**: its contacts, their cases, and their BRIO records, with BRIO records first and Penbox cases folded below. You can draft a reply before any case exists, and open a case in Penbox right next to the attach button.
An email exchanged only between colleagues gets a single, simpler view, with your colleagues shown as greyed chips. On a case, data section badges count the filled rows, and the next step suggested is the same on every email of the case.
# Cases that find their owner
A case with no owner now gets one the first time a colleague **works on it**: edits its data, changes its status, adds a note, sends an email or a form, or creates a BRIO claim. Simply viewing a case doesn't count, and neither do automations, API calls, or the assistant. A case that already has an owner keeps it.
The sidebar helps you spot what's new: a **blue dot** marks the case templates with cases you haven't seen yet, a **No template** line counts the cases without one, drafts no longer inflate the counts, and full template names are shown. Drag the sidebar edge to widen it.
# Credits for everyone, and signatures for shared addresses
* **Credits.** Every member of a workspace can now open the **Credits** page and follow its consumption. It replaces the former Usage tab. See [Credits](/workspace/credits).
* **Signatures for workspace addresses.** Addresses on your authenticated domain and connected workspace mailboxes can now carry an email signature of their own, not only personal mailboxes.
* **Mailbox labels.** A connected mailbox can stop adding the case label in Gmail or Outlook.
* **Personal mailboxes** show which workspace they file their cases into.
# Improvements and fixes
* **Inbox export** adds the owner's name, the parent case status, the language, and the case's key lifecycle dates.
* **Tasks.** The review task closes when its request is marked as processed.
* **BRIO.** The "Create a BRIO activity" automation can force the activity in when its assignee is not working that day. It is off by default.
* **Signatures.** When only initials are placed, the signature page is still added at the end of the document.
* **Email templates.** The visual editor warns you only when switching would actually lose content.
* **Email.** Attachments that Microsoft Defender delivers late are kept.
* **Template versions.** Moving open cases to a newer template version keeps their BRIO links.
* **Outlook.** The agent conversation survives a restart of the add-in.
# Iframe Embedding
Source: https://docs.penbox.io/forms/embedding
Embed Penbox forms directly in your website using iframes
Penbox allows you to embed forms directly in your website using iframes, so your users never leave your application.
## How It Works
Instead of redirecting users to a Penbox URL, you load the form inside an `