personalia-node
v1.0.6
Published
A TypeScript/Node.js client library for the Personalia API to generate personalized PDFs, PNGs, and JPGs
Maintainers
Readme
A TypeScript/Node.js client library for the Personalia API, which allows you to generate personalized or versioned JPG/PNG images and PDF output from your Adobe InDesign templates.
About Personalia
Personalia is a powerful platform for creating personalized content at scale. With Personalia, you can:
- Generate personalized PDFs, PNGs, and JPGs from Adobe InDesign templates
- Create dynamic content for marketing campaigns, certificates, badges, and more
- Automate content creation workflows with a robust API
- Integrate with your existing systems and processes
Visit personalia.io to learn more. Personalia is currently in Beta Testing and is available for use via a Beta account - available at https://www.personalia.io/personalia-beta-signup/
For more information email [email protected]
Installation
npm install personalia-nodeFeatures
- Template Field Discovery: Retrieve template fields to understand what data is required
- Content Generation: Create personalized PDFs, PNGs, and JPGs from templates
- Automatic Polling: Built-in polling mechanism to wait for content generation completion
- Content URL Creation: Generate on-demand URLs that create content when accessed
- Robust Error Handling: Comprehensive error handling with detailed error messages
- TypeScript Support: Full TypeScript type definitions for improved developer experience
Quick Start
This example shows a basic workflow with error handling:
import { PersonaliaClient } from 'personalia-node';
import * as fs from 'fs';
import * as path from 'path';
// Initialize with your API key
const client = new PersonaliaClient('YOUR_API_KEY');
async function generatePDF() {
try {
// 1. Create content request
const createResponse = await client.createContent({
TemplateId: 'your-template-id',
Fields: {
Product: 'iron',
Offer: '20%',
Price: '$25',
'Expiry Date': '2025-04-01'
},
Output: {
Format: 'PDF',
Quality: 'Print'
}
});
console.log(`Request ID: ${createResponse.RequestId}`);
// 2. Poll for completion
const contentResponse = await client.pollForContent(createResponse.RequestId);
// 3. Check if content is ready and has URLs
if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
// 4. Download the content
const pdfUrl = contentResponse.URLs[0];
const response = await fetch(pdfUrl);
const arrayBuffer = await response.arrayBuffer();
const content = Buffer.from(arrayBuffer);
// 5. Save to file
fs.writeFileSync('output.pdf', content);
console.log('PDF saved successfully!');
}
} catch (error) {
console.error('Error:', error);
}
}
// Run the function
generatePDF();Error Handling
The Personalia Node.js client provides comprehensive error handling through the PersonaliaError class. All errors thrown by the client will be instances of PersonaliaError, which extends the standard JavaScript Error class with additional properties:
statusCode: The HTTP status code of the response (if available)errorCode: The Personalia error code (if available)errorId: The error ID from the API (if available)
Common Error Scenarios
1. Synchronous Errors (from POST /v1/content)
These errors occur immediately when making an API request with invalid data:
try {
const response = await client.createContent({
TemplateId: 'invalid-template',
Fields: { /* ... */ }
});
} catch (error) {
if (error instanceof PersonaliaError) {
console.error(`Error ${error.errorCode}: ${error.message}`);
console.error(`Status: ${error.statusCode}`);
// Handle specific error codes
if (error.errorCode === 101) {
console.error('API key does not belong to this template');
} else if (error.errorCode === 103) {
console.error('Invalid Template ID');
}
} else {
console.error('Unexpected error:', error);
}
}2. Asynchronous Errors (from GET /v1/content)
These errors occur during content generation and are detected when polling for results:
try {
const response = await client.pollForContent(requestId);
} catch (error) {
if (error instanceof PersonaliaError) {
console.error(`Content generation failed: ${error.message}`);
// Check for specific error conditions
if (error.errorCode === 107) {
console.error('Failed to fetch a URL in your template');
} else if (error.errorCode === 117) {
console.error('Insufficient credits - please upgrade your plan');
}
}
}3. Network and Request Errors
These errors occur when there are network issues or the server is unreachable:
try {
const response = await client.createContent(/* ... */);
} catch (error) {
if (error instanceof PersonaliaError) {
if (error.statusCode === 0) {
console.error('Network error - please check your internet connection');
} else {
console.error(`API error: ${error.message}`);
}
}
}Complete Error Reference
| Error Code | Description | Possible Solution | |------------|-------------|-------------------| | 101 | API key does not belong to this template | Verify your API key and template ID | | 102 | Invalid or missing API key | Check your API key | | 103 | Invalid Template ID | Verify the template ID | | 104 | Invalid value(s) in the Output section | Check your output parameters | | 105 | Too many Fetch URLs | Reduce the number of Fetch URLs in your template | | 106 | Total Fetch URL size too large | Reduce the size of your Fetch URLs | | 107 | Failed to fetch a URL | Check the URL and try again | | 108 | Invalid/unsupported image format | Use PNG or JPG images | | 109 | Invalid JSON syntax | Check your request body | | 111 | Missing required field | Include all required fields | | 112 | Invalid date format | Use YYYY-MM-DD format | | 113 | Invalid number format | Check number formatting | | 114 | Invalid Fetch protocol | Use HTTP or HTTPS | | 117 | Insufficient credits | Upgrade your plan | | 118 | Unsupported output format | Use a supported format | | 1000-1009 | Server-side errors | Contact support |
Complete Workflow Example
Here's a complete example that demonstrates how to:
- Get template information
- Create a content request
- Poll for completion
- Download and save the content
import { PersonaliaClient } from 'personalia-node';
import * as fs from 'fs';
import * as path from 'path';
async function generatePersonalizedContent() {
// Initialize the client with your API key
const client = new PersonaliaClient('YOUR_API_KEY');
const templateId = 'your-template-id';
const outputDir = './output';
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
try {
// Step 1: Get template information to understand required fields
console.log('Getting template information...');
const templateInfo = await client.getTemplateInfo(templateId);
console.log('Template fields:');
templateInfo.Fields.forEach(field => {
console.log(`- ${field.Name} (${field.Type})`);
});
// Step 2: Create content request with the required fields
console.log('\nSubmitting content creation request...');
const createResponse = await client.createContent({
TemplateId: templateId,
Fields: {
// Populate with actual field values based on templateInfo.Fields
Product: 'iron',
Offer: '20%',
Price: '$25',
'Expiry Date': '2025-04-01'
},
Output: {
Format: 'PDF',
Quality: 'Print',
StrictPolicy: true
}
});
console.log(`Request submitted. Request ID: ${createResponse.RequestId}`);
// Step 3: Use the built-in polling mechanism
console.log('\nPolling for content completion...');
const contentResponse = await client.pollForContent(
createResponse.RequestId,
30, // Maximum 30 attempts
2000 // 2 second interval between attempts
);
// Step 4: Handle the generated content
console.log('\nContent details:');
console.log(`Status: ${contentResponse.Status}`);
if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
console.log(`Content URLs: ${contentResponse.URLs.length} available`);
// Step 5: Download the content
const contentUrl = contentResponse.URLs[0];
console.log(`Downloading from: ${contentUrl}`);
const response = await fetch(contentUrl);
if (!response.ok) {
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
}
// Get content type to determine file extension
const contentType = response.headers.get('content-type') || '';
let extension = 'pdf'; // Default
if (contentType.includes('png')) extension = 'png';
else if (contentType.includes('jpg') || contentType.includes('jpeg')) extension = 'jpg';
// Save the file
const filename = path.join(outputDir, `personalia-${createResponse.RequestId}.${extension}`);
const arrayBuffer = await response.arrayBuffer();
const content = Buffer.from(arrayBuffer);
fs.writeFileSync(filename, content);
console.log(`File saved to: ${filename}`);
console.log(`File size: ${(content.length / 1024).toFixed(2)} KB`);
} else if (contentResponse.Status === 'Failed') {
console.error(`Content generation failed: ${contentResponse.FailureDescription}`);
}
return contentResponse;
} catch (error) {
console.error('Error:', error instanceof Error ? error.message : error);
throw error;
}
}API Reference
PersonaliaClient
The main class for interacting with the Personalia API.
Constructor
const client = new PersonaliaClient(apiKey: string, baseUrl?: string);apiKey: Your Personalia API key (required)baseUrl: Optional custom API base URL (defaults to 'https://api.personalia.io')
Methods
getTemplateInfo
Retrieves information about a template, including its required fields.
async getTemplateInfo(templateId: string): Promise<GetTemplateInfoResponse>Example:
const templateInfo = await client.getTemplateInfo('template-123');
console.log(templateInfo.Fields); // Array of field definitionscreateContent
Submits a request for content creation (PDF, PNG, or JPG).
async createContent(request: CreateContentRequest): Promise<CreateContentResponse>Example:
const response = await client.createContent({
TemplateId: 'template-123',
Fields: {
Product: 'iron',
Offer: '20%',
Price: '$25',
'Expiry Date': '2025-04-01'
},
Output: {
Format: 'PDF',
Quality: 'Print',
StrictPolicy: true
}
});
console.log(response.RequestId); // Use this ID to poll for contentgetContent
Retrieves the status and URLs for a previously submitted content request.
async getContent(requestId: string): Promise<GetContentResponse>Example:
const content = await client.getContent('request-123');
if (content.Status === 'Completed' && content.URLs?.length) {
console.log(`Content available at: ${content.URLs[0]}`);
}pollForContent
Polls for content completion with automatic retries.
async pollForContent(requestId: string, maxAttempts = 30, interval = 2000): Promise<GetContentResponse>Example:
// Will automatically retry until content is ready or max attempts reached
const content = await client.pollForContent('request-123', 20, 3000);
if (content.Status === 'Completed') {
console.log('Content ready for download!');
}createContentUrl
Creates a content on-demand URL that generates content when accessed.
async createContentUrl(request: CreateContentRequest): Promise<CreateUrlResponse>Example:
const urlResponse = await client.createContentUrl({
TemplateId: 'template-123',
Fields: {
Product: 'iron',
Offer: '20%',
Price: '$25'
},
Output: {
Format: 'PDF',
Quality: 'Print'
}
});
console.log(`Content URL: ${urlResponse.Url}`);Response Types
GetContentResponse
Represents the response from the getContent and pollForContent methods.
interface GetContentResponse {
Status: 'Completed' | 'InProgress' | 'Failed';
URLs?: string[]; // Array of URLs to download the content (when Status is 'Completed')
FailureDescription?: string | null; // Description of the failure (when Status is 'Failed')
}CreateContentResponse
Represents the response from the createContent method.
interface CreateContentResponse {
RequestId: string; // ID to use with getContent or pollForContent
}CreateUrlResponse
Represents the response from the createContentUrl method.
interface CreateUrlResponse {
Url: string; // URL that will generate content on demand
}TemplateInfo
Represents the response from the getTemplateInfo method.
interface TemplateInfo {
TemplateId: string;
Fields: Array<{
Name: string;
Type: string;
}>;
}Example response:
{
"TemplateId": "c790fd3b-10ef-4a58-963b-b688e564eefa",
"Fields": [
{
"Name": "First Name",
"Type": "String"
},
{
"Name": "Machine",
"Type": "Number"
},
{
"Name": "Color",
"Type": "String"
}
]
}Examples
The library includes several example scripts in the examples directory:
Get Template Fields
Retrieve the fields required by a template:
// examples/get-template-fields.ts
import { PersonaliaClient } from '../src/client';
async function getTemplateFields() {
const client = new PersonaliaClient('YOUR_API_KEY');
const templateInfo = await client.getTemplateInfo('YOUR_TEMPLATE_ID');
console.log('Template fields:');
templateInfo.Fields.forEach(field => {
console.log(`- ${field.Name} (${field.Type})${field.Required ? ' (Required)' : ''}`);
});
}Create PDF (Print Quality)
Generate a high-quality PDF and download it:
// examples/create-pdf-print.ts
import { PersonaliaClient } from '../src/client';
import * as fs from 'fs';
import * as path from 'path';
async function createPdfPrint() {
const client = new PersonaliaClient('YOUR_API_KEY');
// Create content request
const createResponse = await client.createContent({
TemplateId: 'YOUR_TEMPLATE_ID',
Fields: { /* your fields */ },
Output: {
Format: 'PDF',
Quality: 'Print'
}
});
// Poll for completion
const contentResponse = await client.pollForContent(createResponse.RequestId);
// Download and save the PDF
if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
const pdfUrl = contentResponse.URLs[0];
const response = await fetch(pdfUrl);
const arrayBuffer = await response.arrayBuffer();
const content = Buffer.from(arrayBuffer);
fs.writeFileSync('output.pdf', content);
}
}Create PNG (High Resolution)
Generate a high-resolution PNG image:
// examples/create-png-high-res.ts
import { PersonaliaClient } from '../src/client';
async function createPngHighRes() {
const client = new PersonaliaClient('YOUR_API_KEY');
// Create content request
const createResponse = await client.createContent({
TemplateId: 'YOUR_TEMPLATE_ID',
Fields: { /* your fields */ },
Output: {
Format: 'PNG',
Resolution: 300, // High resolution (300 DPI)
StrictPolicy: true
}
});
// Poll for completion
const contentResponse = await client.pollForContent(createResponse.RequestId);
// Process the URLs
if (contentResponse.Status === 'Completed' && contentResponse.URLs?.length) {
console.log('PNG URLs:');
contentResponse.URLs.forEach(url => console.log(url));
}
}Create Content URL
Generate a URL that creates content on demand:
// examples/create-content-url.ts
import { PersonaliaClient } from '../src/client';
async function createContentUrl() {
const client = new PersonaliaClient('YOUR_API_KEY');
// Create URL request
const urlResponse = await client.createContentUrl({
TemplateId: 'YOUR_TEMPLATE_ID',
Fields: { /* your fields */ },
Output: {
Format: 'PDF',
Quality: 'Display'
}
});
console.log(`Content URL: ${urlResponse.Url}`);
console.log('This URL will generate content on demand when accessed.');
}Error Handling
The library provides a robust error handling system with the PersonaliaError class, which includes detailed information about API errors:
try {
const content = await client.getContent('invalid-request-id');
} catch (error) {
if (error instanceof PersonaliaError) {
// Access detailed error information
console.error(`Error ${error.statusCode}: ${error.message}`);
console.error(`Error ID: ${error.errorId || 'N/A'}`);
console.error(`Error Code: ${error.errorCode || 'N/A'}`);
// Handle specific error types
if (error.statusCode === 404) {
console.error('Content not found or not ready yet');
} else if (error.statusCode === 401) {
console.error('Authentication failed - check your API key');
} else if (error.errorId === '117') {
console.error('Insufficient credits - please top up your account');
}
} else {
console.error('An unexpected error occurred:', error.message);
}
}A full list of the error codes can be found at https://developer.personalia.io/
Personalia Error Codes
The library maps specific Personalia API error codes to helpful messages and suggested fixes. You can access the full list of error codes:
import { PERSONALIA_ERRORS } from 'personalia';
// Example: Get information about a specific error code
const errorInfo = PERSONALIA_ERRORS['101'];
console.log(`Error message: ${errorInfo.message}`);
console.log(`Suggested fix: ${errorInfo.fix}`);License
MIT
Types
CreateContentRequest
interface CreateContentRequest {
TemplateId: string;
Fields: Record<string, string | number | boolean>;
Output?: {
Format?: 'PDF' | 'JPG' | 'PNG';
Quality?: 'Display' | 'Print';
Resolution?: number;
Package?: boolean;
StrictPolicy?: boolean;
};
}Field Value Formats
Strings: Use double quotes
- Valid:
"Hello","2025-02-21" - Invalid:
'Hello','2025-02-21'
- Valid:
Numbers: No comma separator, only 1 decimal point (optional), can be negative
- Valid:
1000.50,-2000 - Invalid:
1,000.50
- Valid:
Dates: Format must be
YYYY-MM-DD- Valid:
2025-02-21 - Invalid:
21/02/2025
- Valid:
Booleans: Use string values
"1"/"0"(not booleantrue/false)- Valid:
"1"(true/yes/on),"0"(false/no/off) - Invalid:
true,false,1,0(as non-string values)
- Valid:
Output Options
Format:
PDF: PDF documentJPG: JPEG imagePNG: PNG image
Quality (PDF only):
Display: Optimized for screen viewingPrint: High-quality print output
Resolution (images only):
- Range: 72-300 DPI
- Default: 72
Package:
true: Compress output into ZIP filefalse: Return single file (default)
StrictPolicy:
true: Fail on missing assets/fonts/styles (default)false: Ignore issues (may cause unexpected results)
Error Handling
The client includes built-in error handling that will throw errors with descriptive messages. Each error includes an error ID and reason from the API.
try {
await client.createContent({...});
} catch (error) {
// Error will include API error ID and reason if available
console.error(error.message);
// Example: "Personalia API Error 101: API key does not belong to this template"
}Best Practices
API Key Security: Never expose your API key in client-side code. Always keep it secure on your server.
Error Handling: Always implement proper error handling around API calls.
Template Testing: Use
getTemplateInfoto validate your template fields before submitting content requests.Output Format: Choose the appropriate output format and quality for your use case:
- Use
PDFwithPrintquality for documents that need to be printed - Use
JPG/PNGwith appropriate resolution for web/screen display
- Use
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT
Getting Started with Personalia
To use this library, you'll need a Personalia API key. Visit personalia.io to:
- Create an account and access the dashboard
- Create templates using Adobe InDesign
- Generate your API key
- Start creating personalized content at scale
