Creates a new case file in Penneo with the specified documents and signers. This endpoint accepts a multipart/form-data request containing JSON metadata and files.
Open API 3.0 Specs
The Open API 3.0 Specs are available on the following urls:
Case File Data Structure Documentation
This document describes the complete JSON structure for the data field when creating a Penneo case file.
Table of Contents
- Overview
- Root Structure
- Case File Object
- Signers Array
- CC Recipients Array
- Documents Array
- Complete Example
- Minimal Example
Overview
The data field must be a stringified JSON object containing a caseFile property. This structure defines all
aspects of your case file including title, signers, documents, and various configuration options.
Format: The data must be sent as a JSON string, not as a JSON object.
Root Structure
{
"caseFile": {
// Case file configuration (see below)
}
}Properties
| Property | Type | Required | Description |
|---|---|---|---|
caseFile | object | Yes | The main case file configuration object |
Case File Object
The caseFile object contains all configuration for the case file.
Required Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
title | string | The case file title displayed in Penneo | Min: 1 char, Max: 1024 chars |
signers | array | Array of signer objects | At least 1 signer required |
documents | array | Array of document objects | At least 1 signable document required |
Optional CaseFile properties
| Property | Type | Default | Description |
|---|---|---|---|
ccRecipients | array | - | Recipients who receive a copy but don't sign |
language | string | null | null | Case file language. Applied to all signers without a specific language. Options: 'en', 'da', 'sv', 'nl', 'fr', 'fi', 'no', 'de', or null |
metaData | string | - | Metadata searchable in Penneo |
reference | string | - | Reference shown in Penneo web application |
folderId | integer | - | Folder ID where case file should be placed (min: 1) |
sendAt | integer | null | null | Unix timestamp when case file should be sent. null = immediately |
expireAt | integer | null | null | Unix timestamp when case file will expire |
sensitiveData | boolean | false | If true, signers must validate their identity before accessing documents |
accessControl | boolean | false | If enabled, SSN/VATIN/phone is validated before access |
visibilityMode | integer | 0 | Document visibility: 0 = all docs visible, 1 = only docs signer must sign |
disableNotificationsOwner | boolean | false | If true, case file owner won't receive notifications |
disableEmailAttachments | boolean | false | Disables email attachments in finalization emails (overridden if sensitiveData is true) |
signOnMeeting | boolean | false | Enable signing documents on a meeting |
Signers Array
Each signer represents a person who will sign or review the documents.
Required Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
name | string | Name of the signer | Min: 1 character |
Optional Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
email | string | Signer's email address. Penneo handles all communications | Valid email format |
socialSecurityNumberPlain | string | null | Signer's SSN or phone number | E.164 format recommended for phone |
vatin | string | null | VAT identification number of signer's company | - |
role | string | Role identifier for the signer. If no role is provided, "Signer" will be used as the default role for all signers | Min: 3 chars, alphanumeric, underscore, spaces, commas. First char is mandatory alphanumeric. Last char cannot be space. |
onBehalfOf | string | Company/organization that this signer signs on behalf of | - |
signOrder | integer | Signing order (0 = can sign immediately) | Min: 0 |
ssnType | string | Type of SSN/mobile identification | 'legacy', 'dk:cpr', 'se:pin', 'no:nin', 'be:nrn', 'fi:pic', 'sms' |
accessControl | boolean | If enabled, SSN/VATIN/phone is validated before access | - |
successUrl | string | Redirect URL after successful signing | Valid URI, min: 5 chars |
failUrl | string | Redirect URL after signing failure | Valid URI, min: 5 chars |
language | string | null | Signer's preferred language | 'en', 'da', 'sv', 'nl', 'fr', 'fi', 'no', 'de', or null |
emailFormat | string | Email format | 'text' or 'html' |
emailSubject | string | Custom email subject | Min: 5 chars |
emailText | string | Custom email body text | Min: 5 chars |
reminderInterval | integer | Days before reminder is sent | Min: 1 |
reminderEmailSubject | string | Custom reminder email subject | Min: 5 chars |
reminderEmailText | string | Custom reminder email body | Min: 5 chars |
completedEmailSubject | string | Custom completion email subject | Min: 5 chars |
completedEmailText | string | Custom completion email body | Min: 5 chars |
activeAt | integer | null | Unix timestamp when signer becomes active | |
expireAt | integer | null | Unix timestamp when signer access expires | |
enableInsecureSigning | boolean | Enable insecure signing methods | |
insecureSigningMethods | array | If insecure signing methods are enabled, choose which ones are available for the signer | Array of allowed insecure signing methods: 'text', 'draw', 'image' |
storeAsContact | boolean | - | If true, signer will be stored as contact in Penneo |
Documents Array
Each document represents a file to be signed or attached to the case file.
Required Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
title | string | Document title | Min: 1 char, Max: 1024 chars |
name | string | File name (must match uploaded file) | Min: 1 character |
Optional Properties
| Property | Type | Default | Description | Constraints |
|---|---|---|---|---|
signable | boolean | true | If true, creates signable document. If false, document is a non-signable attachment | - |
roles | array | - | Array of role identifiers that can sign this document | Each role: min: 3 chars, alphanumeric, underscore, hyphens, spaces, commas. First char is mandatory alphanumeric. Last char cannot be space. |
documentOrder | integer | - | Display order of the document | Min: 0 |
metaData | string | - | Metadata searchable in Penneo | - |
CC Recipients Array [Optional]
CC Recipients are people who should receive a copy of the case file documents but do not need to sign. They will be notified when the case file is completed.
Required Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
name | string | Name of the recipient. | Min: 1 character |
email | string | Email of the recipient. | Valid email format |
Optional Properties
| Property | Type | Description | Constraints |
|---|---|---|---|
storeAsContact | boolean | Signer will be stored as a contact. Defaults to true. | - |
Minimal Example
Here's a minimal example with only required fields:
{
"caseFile": {
"title": "Simple Agreement",
"signers": [
{
"name": "John Doe",
"email": "[email protected]"
}
],
"documents": [
{
"title": "Agreement Document",
"name": "agreement.pdf"
}
]
}
}Extended Example
Here's a comprehensive example with most fields populated:
Make sure you pass in the files array of the main payload the corresponding files with names matching the
nameproperties in thedocumentsarray. In our example you will have to submit terms.pdf and contract.pdf files.You can also pass a folderId in the caseFile object to place the case file in a specific folder.
{
"caseFile": {
"title": "Commercial Agreement 2025",
"language": "en",
"reference": "CONTRACT-2025-001",
"metaData": "Q4 Commercial Contract",
"sensitiveData": false,
"disableNotificationsOwner": false,
"disableEmailAttachments": false,
"signOnMeeting": false,
"sendAt": null,
"expireAt": null,
"visibilityMode": 0,
"signers": [
{
"name": "John Doe",
"email": "[email protected]",
"role": "Client Signer",
"language": "en",
"socialSecurityNumberPlain": null,
"ssnType": "dk:cpr",
"vatin": null,
"signOrder": 0,
"onBehalfOf": "Acme Corporation",
"accessControl": true,
"storeAsContact": true,
"successUrl": "https://example.com/success",
"failUrl": "https://example.com/failure",
"emailFormat": "html",
"emailSubject": "Please sign the commercial agreement",
"emailText": "Dear John, please review and sign the attached commercial agreement.",
"reminderInterval": 3,
"reminderEmailSubject": "Reminder: Please sign the commercial agreement",
"reminderEmailText": "This is a friendly reminder to sign the agreement.",
"completedEmailSubject": "Thank you for signing",
"completedEmailText": "The agreement has been completed successfully.",
"activeAt": null,
"expireAt": null,
"enableInsecureSigning": false,
"insecureSigningMethods": []
},
{
"name": "Jane Smith",
"email": "[email protected]",
"role": "Company Signer",
"language": "en",
"signOrder": 1,
"onBehalfOf": "Company Ltd",
"accessControl": false,
"storeAsContact": true,
"emailFormat": "text",
"reminderInterval": 2
}
],
"ccRecipients": [
{
"name": "John CC",
"email": "[email protected]",
"storeAsContact": false
},
{
"name": "Jane CC",
"email": "[email protected]",
"storeAsContact": true
}
],
"documents": [
{
"title": "Main Contract",
"name": "contract.pdf",
"signable": true,
"roles": [
"Client Signer",
"Company Signer"
],
"documentOrder": 0,
"metaData": "Primary contract document"
},
{
"title": "Terms and Conditions",
"name": "terms.pdf",
"signable": false,
"metaData": "Standard T&C attachment"
}
]
}
}Usage Notes
1. Sending the Data
The data field must be stringified when sending in a multipart/form-data request:
import { readFileSync } from "fs";
// 1. Build CaseFile JSON data
const caseFileData = {caseFile: {
title: "My Contract",
signers: [
{ name: "John Doe", email: "[email protected]" },
{ name: "Jane Doe", email: "[email protected]" }
],
documents: [
{ title: "Contract", name: "contract.pdf" }, //name matches first uploaded doc
{ title: "Appendix", name: "appendix.pdf" } //name matches second uploaded doc
]
}};
// 2. Load files directly into File objects
const contractPdfBytes = readFileSync("./contract.pdf");
const appendixPdfBytes = readFileSync("./appendix.pdf");
const contractFile = new File([contractPdfBytes], "contract.pdf", {
type: "application/pdf"
});
const appendixFile = new File([appendixPdfBytes], "appendix.pdf", {
type: "application/pdf"
});
// 3. FormData
const formData = new FormData();
// JSON data field
formData.append(
"data",
JSON.stringify(caseFileData) // <-- Append the JSON as a simple string
);
// Add PDF files
formData.append("files", contractFile, contractFile.name);
formData.append("files", appendixFile, appendixFile.name);
// 4. POST request
const response = await fetch(
"https://sandbox.penneo.com/send/api/v1/casefiles/20251022/create",
{
method: "POST",
headers: {
"X-Auth-Token": "YOUR_TOKEN_HERE" // Retrive the JWT Token based on https://penneo.readme.io/v1.3/docs/using-oauth#/api-keys-grant
},
body: formData
}
);
// 5. Handle Penneo response
if (!response.ok) {
console.error("Status code:", await response.status);
console.error("Error:", await response.text());
process.exit(1);
}
console.log("Success:", await response.json());
2. Role Matching
- If you specify
rolesin documents, only signers with matchingrolevalues can sign those documents - Roles must be at least 3 characters and contain only alphanumeric characters and underscores (no spaces)
- If no roles are specified on documents, all signers can sign all documents
3. File Matching
- The
nameproperty in each document must match the filename of the uploaded PDF
4. Timestamps
All timestamp fields (sendAt, expireAt, activeAt) use Unix epoch time (seconds since January 1, 1970 UTC).
// JavaScript example to get Unix timestamp
const timestamp = Math.floor(Date.now() / 1000);Error Handling
If validation fails, you'll receive a 400 Bad Request response with details about which fields failed validation.
Common errors:
- Missing required fields: Ensure
title,signers, anddocumentsare provided - Invalid role format: Roles cannot contain spaces or special characters (except underscore)
- Email format: Must be a valid email address
- String length: Check minimum/maximum length requirements
- Invalid enum values: Language, ssnType, emailFormat must use exact values listed above
- File mismatch: Document
namemust match uploaded PDF filename
Credentials & Testing
To interact with the Penneo API, you need to authenticate using JWT tokens.
Follow these steps:
- Make sure you have your OAuth Client ID and Client Secret for the environment you are implementing. If you don't have a set, contact Penneo support to have them issued.
- Generate a JWT token as described in the Authentication Guide using API Keys Grant.
- With the JWT Token, you can make authorized requests to this endpoint for the lifetime of the JWT Token.
- Once you submit a CaseFile, you can check its status using the
Check job status by UUIDendpoint. We support polling and we encourage you to check for a job status no less than every 5 seconds until the job is complete.
