Template Converter API
Overview
The Template Converter API allows you to convert presentation templates between different formats, transform templates to match your brand guidelines, and manage template conversions programmatically. This API enables seamless integration of template conversion workflows into your applications.
Use it to:
- Convert templates between different presentation formats
- Transform templates to align with your brand guidelines
- Batch process multiple template conversions
- Track conversion status and retrieve converted templates
- Manage template conversion jobs
Authentication
All endpoints require API key-based authentication. Add the API key in the request Authorization header using the Bearer token format:
Example:
Authorization: Bearer your-api-key
Common Headers
All responses include CORS headers:
{
"Access-Control-Allow-Origin": "*"
}
Key Endpoints
GET /api/v1/template-converter/templates– Get list of available templatesPOST /api/v1/template-converter/preprocess– Upload file chunks for preprocessingPOST /api/v1/template-converter/finalprocess– Finalize file upload and get file IDPOST /api/v1/template-converter/start– Convert presentation to a different templateGET /api/v1/template-converter/status/{callbackId}– Get conversion job statusGET /api/v1/template-converter/download/{callbackId}– Download converted templateGET /api/v1/template-converter/{callbackId}/review-suggestions– Get review suggestions for converted templatePATCH /api/v1/template-converter/{callbackId}/review-suggestions– Update review suggestionsGET /api/v1/template-converter/work-area-options– Get work area adjustment options for a slidePOST /api/v1/template-converter/adjust-work-area– Apply work area adjustment to a slideGET /api/v1/template-converter/layouts– Get available layouts for slidesPOST /api/v1/template-converter/update-layout– Apply a new layout to slidesGET /api/v1/template-converter/modify-format-settings– Get modify format settings for slidesPOST /api/v1/template-converter/modify-format– Apply format modifications to slidesPOST /api/v1/template-converter/{callbackId}/template-change– Change template for converted presentationPUT /api/v1/template-converter/reaction-feedback– Submit feedback and reactions
Get Templates List
Retrieve a list of available templates for template conversion. The code field from the response should be used in the start and template-change APIs as the templateName parameter.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/templates
Query Parameters
- No query parameters required
{
"status": "success",
"log": "success",
"data": [
{
"code": "1234567890_abbvie",
"name": "AUG_19"
},
{
"code": "abbvie_009_251",
"name": "abbvie_temp_009"
},
{
"code": "abbvie_0098_254",
"name": "abbvie_0098"
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
| status | String | Always "success" for successful requests |
| log | String | Status log message |
| data | Array | List of available templates |
| data[].code | String | Template code to use in start and template-change APIs (use as templateName) |
| data[].name | String | Display name of the template |
Important Notes:
- Use the
codevalue from the response as thetemplateNameparameter in:POST /api/v1/template-converter/startAPIPOST /api/v1/template-converter/{callbackId}/template-changeAPI
Error Retrieving Templates (400)
{
"error": "BAD_REQUEST",
"details": "Error retrieving template converter templates",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Preprocess File
Upload file content in chunks to the server for processing. This API handles chunked file uploads, validates file types and filenames, and prepares files for final processing. A unique code is automatically prepended to the filename to ensure uniqueness.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/preprocess
Request Body
{
"fileContent": "data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,{base64_chunk}", // Required: Base64-encoded file content with data URL format
"requestIdentifier": "uuid-string", // Required: Unique identifier for this upload request (UUID format)
"fileName": "presentation.pptx", // Required: Name of the file including extension (max 100 characters)
"chunkIndex": 0, // Required: Zero-based index of the current chunk (>= 0)
"totalChunks": 1 // Required: Total number of chunks for this file (> 0)
}
Field Validation Rules
| Field | Type | Validation Rules |
|---|---|---|
| fileContent | String | Required, must not be empty, must include data URL format |
| fileName | String | Required, 1-100 characters, no special characters, no multiple extensions |
| chunkIndex | Number | Required, must be >= 0 |
| totalChunks | Number | Required, must be > 0 |
| requestIdentifier | String | Required, UUID format |
Supported File Types: pptx, ppt
Important Notes:
- For files under 5MB: Send as a single chunk (totalChunks = 1, chunkIndex = 0).
- For files over 5MB: Split into 5MB chunks and send parallel requests with the same requestIdentifier.
- Wait for all chunk uploads to complete successfully before calling the finalprocess API.
{
"message": "File chunk received successfully",
"requestIdentifier": "13305be2-469d-4dbf-9813-aad096d266c1",
"fileName": "abc123_redesign_demo_deck.pptx"
}
Missing Required Field (400)
{
"status": "failed",
"error": "MISSING_REQUIRED_FIELD",
"details": "Missing required fields in request"
}
Unsupported File Type (400)
{
"status": "failed",
"error": "UNSUPPORTED_FILE_TYPE",
"details": "File 'report.exe' has unsupported file type. Supported types: pptx, docx, txt, pdf, xls, xlsx, csv",
"supportedTypes": ["pptx", "docx", "txt", "pdf", "xls", "xlsx", "csv"]
}
Invalid Input (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "Filename length must be between 1 and 100 characters"
}
Finalprocess File
Finalizes the file upload process and returns file information including the file ID needed for template conversion. This API processes file identifiers from previous preprocess operations and returns detailed file information.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/finalprocess
Request Body
{
"fileIdentifier": "13305be2-469d-4dbf-9813-aad096d266c1", // Required: The requestIdentifier from preprocess API response
"fileName": "redesign_demo_deck.pptx" // Required: Original file name (without prepended code)
}
Required Fields:
fileIdentifier: The unique identifier returned from the preprocess API responsefileName: The original file name (without the prepended unique code)
{
"status": "success",
"log": "File uploaded successfully",
"data": {
"fileId": "3c3828a0-3b2e-4cea-bf1f-f933023b34d9", // Use this fileId in start API request
"s3Prefix": "protected/prezentcache01/.../redesign_demo_deck5.pptx",
"s3Bucket": "mvp-assets-uatstage",
"type": "s3",
"sizeKb": 145.884,
"numOfPages": 0
}
}
Important: Use the fileId from the response in the start API request.
{
"status": "failed",
"error": "FILE_NOT_FOUND",
"details": "File identifier not found or invalid"
}
Start Template Conversion
Convert a presentation to a different template using the file ID obtained from the finalprocess API.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/start
Request Body
{
"fileId": "a47aacc2-a23b-4782-94d4-f6dc76f9cdca", // Required: File ID from finalprocess API response
"templateName": "abbvie_aquipta", // Required: Name of the target template
"includeImageWithoutData": false, // Optional: Include images without data (default: false)
"includeImageWithData": false, // Optional: Include images with data (default: false)
"includeIcons": false, // Optional: Include icons (default: false)
"includeSpecialColor": false, // Optional: Include special colors (default: false)
"settings": { // Optional: Default settings for conversion
"ai_mode": "standard", // Optional: AI processing mode ("standard" or "thinking")
"work_area_option": "auto-adjust", // Optional: Default work area adjustment option
"modifyFormat": { "title": "target" } // Optional: Title format source ("source" or "target")
}
}
Required Fields:
fileId: The unique file identifier obtained from the finalprocess API responsetemplateName: The name of the target template to convert to
Optional Fields:
includeImageWithoutData: Boolean - Include images without data in the conversion (default:false)includeImageWithData: Boolean - Include images with data in the conversion (default:false)includeIcons: Boolean - Include icons in the conversion (default:false)includeSpecialColor: Boolean - Include special colors in the conversion (default:false)settings: Object - Default settings for the conversion processsettings.ai_mode: String - AI processing mode. Valid values:"standard","thinking"settings.work_area_option: String - Default work area adjustment to apply. Valid values:"no-adjust","auto-adjust","scale-to-fit","auto-adjust-la"settings.modifyFormat: Object - Title format source settingsettings.modifyFormat.title: String - Use"source"to keep original title formatting or"target"to apply target template formatting (default:"target")
{
"callbackId": "d437b021-1ff7-4cb1-b906-69a9a8cb913e",
"status": "success",
"presentationName": "redesign_demo_deck5.pptx"
}
Important: Use the callbackId from this response to check status and download the converted file.
{
"status": "failed",
"error": "INVALID_FILE_ID",
"details": "The specified file ID was not found"
}
{
"status": "failed",
"error": "INVALID_TEMPLATE",
"details": "The specified template name is not available"
}
Get Conversion Status
Check the status of a template conversion job using the callback ID returned from the start API.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/status/{callbackId}
Path Parameters
callbackId: The callback ID returned from the start API response
{
"callbackId": "36fc16ff-7d65-4b91-b011-a07cb91b6ba3",
"status": "processing",
"presentationName": "redesign_demo_deck5.pptx",
"templateName": "abbvie_aquipta"
}
Status Values:
processing: Conversion is in progresscompleted: Conversion completed successfullyfailed: Conversion failed
{
"status": "failed",
"error": "CALLBACK_ID_NOT_FOUND",
"details": "The specified callback ID was not found"
}
Download Converted Template
Get the download URL for the converted template file.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/download/{callbackId}
Path Parameters
callbackId: The callback ID returned from the start API response
{
"status": "success",
"downloadUrl": "https://static-devstaging.myprezent.com/private/template-converter/comply/.../mergeslides/b6928199-ea63-458d-8d5d-52d547cdefb2.pptx",
"fileName": "redesign_demo_deck.pptx",
"message": "Download URL generated successfully"
}
Note: Use the downloadUrl to download the converted file. The URL includes authentication tokens and expires after a certain period.
Missing Callback ID (400)
{
"success": false,
"data": null,
"error": {
"message": "Bad request.",
"details": {
"details": "callback_id is required"
}
}
}
Callback ID Not Found or Expired (400)
{
"success": false,
"data": null,
"error": {
"message": "Bad request.",
"details": {
"details": "Callback ID not found. The conversion may have expired or is invalid."
}
}
}
Template Conversion Not Completed (400)
{
"success": false,
"data": null,
"error": {
"message": "Bad request.",
"details": {
"details": "Template conversion is not completed yet. Current status: InProgress"
}
}
}
Get Review Suggestions
Retrieve review suggestions for color and special color changes in the converted template.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/{callbackId}/review-suggestions
Path Parameters
callbackId: The callback ID returned from the start API response
{
"callbackId": "c927ed06-9c76-4afd-a8c2-1edb5cd84da3",
"status": "success",
"presentationName": "ArtificialIntelligenceEvolution.pptx",
"suggestions": {
"colorSuggestions": {
"status": "accepted",
"values": [
{
"oldColor": "ff313541",
"suggestedColor": "ff374550"
}
]
},
"specialColorSuggestions": {
"status": "accepted",
"values": [
{
"oldColor": "ff313541",
"suggestedColor": "ff374550"
}
]
}
}
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callback_id is required",
"statusCode": 400
}
Failed to Retrieve Status (400)
{
"error": "BAD_REQUEST",
"details": "Failed to retrieve template converter status",
"statusCode": 400
}
No Compliance Data Found (400)
{
"error": "BAD_REQUEST",
"details": "No compliance data found for this callback_id",
"statusCode": 400
}
No Format Suggestions Found (400)
{
"error": "BAD_REQUEST",
"details": "No format suggestions found in compliance data",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Update Review Suggestions
Update review suggestions by accepting, rejecting, or editing color suggestions.
- Request
- Success Response
- Error Response
PATCH /api/v1/template-converter/{callbackId}/review-suggestions
Path Parameters
callbackId: The callback ID returned from the start API response
Request Body
{
"colorSuggestions": {
"status": "edited",
"values": {
"newColor": "cbfc86",
"currentColor": "ff071d49"
}
},
"specialColorSuggestions": {
"status": "edited",
"values": {
"newColor": "cbfc86",
"currentColor": "ff071d49"
}
}
}
Required Fields:
- At least one of
colorSuggestionsorspecialColorSuggestionsmust be provided
If colorSuggestions is provided:
colorSuggestions.status: Status must be"edited"(required)colorSuggestions.values: Object (required)colorSuggestions.values.newColor: User selected new color value in hex format without # (required)colorSuggestions.values.currentColor: ThesuggestedColorfrom the GET review suggestions API response (required)
If specialColorSuggestions is provided:
specialColorSuggestions.status: Status must be"edited"(required)specialColorSuggestions.values: Object (required)specialColorSuggestions.values.newColor: User selected new color value in hex format without # (required)specialColorSuggestions.values.currentColor: ThesuggestedColorfrom the GET review suggestions API response (required)
Note: Both colorSuggestions and specialColorSuggestions can be provided together, but at least one is required. The currentColor should match the suggestedColor from the GET review suggestions API response.
{
"status": "success",
"message": "Review suggestions updated successfully",
"callbackId": "c927ed06-9c76-4afd-a8c2-1edb5cd84da3"
}
Invalid JSON (400)
{
"error": "INVALID_JSON",
"details": "Please provide a valid JSON payload",
"statusCode": 400
}
Missing Required Fields (400)
{
"error": "BAD_REQUEST",
"details": "At least one of colorSuggestions or specialColorSuggestions is required",
"statusCode": 400
}
Invalid Status (400)
{
"error": "BAD_REQUEST",
"details": "colorSuggestions.status must be \"edited\"",
"statusCode": 400
}
Missing Values Object (400)
{
"error": "BAD_REQUEST",
"details": "colorSuggestions.values object is required",
"statusCode": 400
}
Missing New Color (400)
{
"error": "BAD_REQUEST",
"details": "colorSuggestions.values.newColor is required",
"statusCode": 400
}
Missing Current Color (400)
{
"error": "BAD_REQUEST",
"details": "colorSuggestions.values.currentColor is required",
"statusCode": 400
}
Template Conversion Not Completed (400)
{
"error": "BAD_REQUEST",
"details": "Template conversion is not completed yet. Current status: {status}",
"statusCode": 400
}
Failed to Fetch Status (400)
{
"error": "BAD_REQUEST",
"details": "Callback ID not found. Failed to fetch template converter status",
"statusCode": 400
}
Compliance Data Not Found (400)
{
"error": "BAD_REQUEST",
"details": "TC-API-FetchSplitSlideDeckCompliance data not found in status response",
"statusCode": 400
}
Format Suggestions Not Found (400)
{
"error": "BAD_REQUEST",
"details": "format_suggestions not found in TC-API output",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Get Work Area Options
Retrieve work area adjustment options for a specific slide. Work area adjustments help optimize how content fits within the slide layout.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/work-area-options
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| callbackId | String | Yes | The callback ID from the start API response |
| slideIndex | Number | Yes | The zero-based index of the slide to get options for |
{
"callback_id": "8cf1a00e-b097-44b0-abd7-36dfbb5e5311",
"work_area_adjustments": [
{
"label": "No Adjust",
"value": "no-adjust",
"png": "https://static4.myprezent.com/protected/prezentcache01/.../06575388-4f03-4fef-97ef-9bcc16e87892_002_000.png"
},
{
"label": "Auto Adjustment (Preserve Aspect Ratio)",
"value": "auto-adjust",
"png": "https://static4.myprezent.com/protected/prezentcache01/.../lock-aspect-ratio/ecf01998-3f05-4db0-b93f-cc9e24bd080a_002_000.png"
},
{
"label": "Auto Adjustment (Scale to Fit)",
"value": "scale-to-fit",
"png": "https://static4.myprezent.com/protected/prezentcache01/.../scale-to-fit/ecf01998-3f05-4db0-b93f-cc9e24bd080a_002_000.png"
},
{
"label": "Auto adjustment(Preserve Aspect Ratio, Left-Aligned)",
"value": "auto-adjust-la",
"png": "https://static4.myprezent.com/protected/prezentcache01/.../lock-aspect-ratio-la/ecf01998-3f05-4db0-b93f-cc9e24bd080a_002_000.png"
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
| callback_id | String | Unique identifier for the work area options request |
| work_area_adjustments | Array | List of available work area adjustment options |
| work_area_adjustments[].label | String | Display label for the adjustment option |
| work_area_adjustments[].value | String | Value to use when applying this adjustment |
| work_area_adjustments[].png | String | Preview image URL showing the adjustment result |
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "upload_id query parameter is required",
"statusCode": 400
}
Missing Slide Index (400)
{
"error": "BAD_REQUEST",
"details": "slideIndex query parameter is required",
"statusCode": 400
}
No Options Found for Slide (400)
{
"error": "BAD_REQUEST",
"details": "No work area options found for slideIndex: 5",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Adjust Work Area
Apply a work area adjustment to a specific slide. This updates how content fits within the slide layout.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/adjust-work-area
Request Body
{
"callbackId": "01977761-5b38-4c00-afb6-c3015dc25e30",
"slideIndex": 0,
"selection": "auto-adjust"
}
Required Fields:
| Field | Type | Description |
|---|---|---|
| callbackId | String | The callback ID from the start API response |
| slideIndex | Number | The zero-based index of the slide to adjust |
| selection | String | The work area adjustment to apply |
Valid Selection Values:
no-adjust- Keep original layout without adjustmentsauto-adjust- Auto adjustment preserving aspect ratioscale-to-fit- Scale content to fit the work areaauto-adjust-la- Auto adjustment with left alignment
{
"status": "success",
"message": "Work area adjustment updated successfully"
}
Invalid JSON (400)
{
"error": "BAD_REQUEST",
"details": "Please provide a valid JSON payload",
"statusCode": 400
}
Missing Selection (400)
{
"error": "BAD_REQUEST",
"details": "selection is required in payload",
"statusCode": 400
}
Missing Slide Index (400)
{
"error": "BAD_REQUEST",
"details": "slideIndex is required in payload",
"statusCode": 400
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callbackId is required in payload",
"statusCode": 400
}
Update Failed (400)
{
"error": "BAD_REQUEST",
"details": "Failed to update work area",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Get Layouts
Retrieve available layouts for applying to slides. Returns both target template layouts and input deck layouts (if available).
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/layouts
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| callbackId | String | Yes | The callback ID from the start API response |
| slideIndex | Number | Yes | The zero-based index of the slide to get layouts for |
{
"callbackId": "01977761-5b38-4c00-afb6-c3015dc25e30",
"status": "success",
"layouts": {
"targetTemplateLayouts": [
{
"layoutId": "tt_layout_001",
"layout_display_name": "Title Slide",
"layout_preview": "https://static4.myprezent.com/protected/prezentcache01/.../layout_preview.png"
},
{
"layoutId": "tt_layout_002",
"layout_display_name": "Content with Image",
"layout_preview": "https://static4.myprezent.com/protected/prezentcache01/.../layout_preview.png"
}
],
"inputDeckLayouts": [
{
"layoutId": "id_layout_001",
"layout_display_name": "Original Layout 1",
"layout_preview": "https://static4.myprezent.com/protected/prezentcache01/.../layout_preview.png"
}
]
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| callbackId | String | The callback ID used in the request |
| status | String | Status of the request: success, inprogress, or failed |
| layouts | Object | Container for layout options |
| layouts.targetTemplateLayouts | Array | Layouts from the target template |
| layouts.inputDeckLayouts | Array | Layouts extracted from the original input deck (may be empty if still processing) |
| layouts.*.layoutId | String | Unique identifier for the layout (use in update-layout API) |
| layouts.*.layout_display_name | String | Display name of the layout |
| layouts.*.layout_preview | String | Preview image URL for the layout |
Status Values:
success- All layouts are readyinprogress- Input deck layout extraction is still in progressfailed- Input deck layout extraction failed (target layouts still available)
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callbackId query parameter is required",
"statusCode": 400
}
Missing Slide Index (400)
{
"error": "BAD_REQUEST",
"details": "slideIndex query parameter is required",
"statusCode": 400
}
Reference Template Not Found (400)
{
"error": "BAD_REQUEST",
"details": "Could not find reference template for the given callbackId",
"statusCode": 400
}
No Layouts Data (400)
{
"error": "BAD_REQUEST",
"details": "No layouts data found in response",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Update Layout
Apply a new layout to one or more slides. This initiates a processing workflow and returns a new callback ID for tracking.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/update-layout
Request Body
{
"callbackId": "01977761-5b38-4c00-afb6-c3015dc25e30",
"slideIndex": [0, 1, 2],
"layoutId": "tt_layout_001",
"source": "targetTemplateLayouts",
"selection_type": "Apply to custom slides"
}
Required Fields:
| Field | Type | Description |
|---|---|---|
| callbackId | String | The callback ID from the start API response |
| slideIndex | Array | Array of zero-based slide indices to apply the layout to (must have at least one element) |
| layoutId | String | The layout ID from the get-layouts API response |
| source | String | Source of the layout: targetTemplateLayouts or inputDeckLayouts |
Optional Fields:
| Field | Type | Description |
|---|---|---|
| selection_type | String | How the slides were selected. If not provided, automatically determined based on slideIndex |
Source Values:
targetTemplateLayouts- Use a layout from the target templateinputDeckLayouts- Use a layout from the original input deck
Selection Type (auto-determined if not provided):
single- If slideIndex has exactly one elementApply to all slides- If slideIndex covers all slides in the presentationApply to custom slides- For any other selection
{
"status": "processing",
"callbackId": "new-callback-id-for-tracking"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| status | String | Always processing for successful initiation |
| callbackId | String | New callback ID to use for checking the update status via the status API |
Next Steps:
Use the new callbackId from the response to poll the existing status API until the layout update is complete:
GET /api/v1/template-converter/status/{new-callbackId}
Example workflow:
// 1. Call update-layout API
const updateResponse = await fetch('/api/v1/template-converter/update-layout', {
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
callbackId: 'original-callback-id',
slideIndex: [0, 1],
layoutId: 'tt_layout_001',
source: 'targetTemplateLayouts'
})
});
const { callbackId: newCallbackId } = await updateResponse.json();
// 2. Poll status API with the new callbackId until complete
let status = 'processing';
while (status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
const statusResponse = await fetch(`/api/v1/template-converter/status/${newCallbackId}`, {
headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
});
const statusData = await statusResponse.json();
status = statusData.status;
}
// 3. When status is 'completed', the layout update is finished
Invalid JSON (400)
{
"error": "BAD_REQUEST",
"details": "Invalid JSON in request body",
"statusCode": 400
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callbackId is required in the request body",
"statusCode": 400
}
Invalid Slide Index (400)
{
"error": "BAD_REQUEST",
"details": "slideIndex must be an array with at least one element",
"statusCode": 400
}
Missing Layout ID (400)
{
"error": "BAD_REQUEST",
"details": "layoutId is required in the request body",
"statusCode": 400
}
Missing Source (400)
{
"error": "BAD_REQUEST",
"details": "source is required in the request body",
"statusCode": 400
}
Invalid Source (400)
{
"error": "BAD_REQUEST",
"details": "source must be either \"targetTemplateLayouts\" or \"inputDeckLayouts\"",
"statusCode": 400
}
Layout Not Found (400)
{
"error": "BAD_REQUEST",
"details": "Layout with ID \"invalid_id\" not found in target template layouts",
"statusCode": 400
}
Input Layouts Not Available (400)
{
"error": "BAD_REQUEST",
"details": "Input deck layouts are not available for this callbackId",
"statusCode": 400
}
Input Layouts Not Ready (400)
{
"error": "BAD_REQUEST",
"details": "Input deck layouts are not ready or extraction failed",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Get Modify Format Settings
Retrieve modify format settings for all slides of a completed template conversion. Returns current settings if format modifications have been applied previously, otherwise returns default settings derived from the conversion.
- Request
- Success Response
- Error Response
GET /api/v1/template-converter/modify-format-settings
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| callbackId | String | Yes | The callback ID from the start API response |
{
"callbackId": "c927ed06-9c76-4afd-a8c2-1edb5cd84da3",
"modify_format_settings": [
{
"title": {
"font_height": { "option_type": "target", "value": "" },
"font_bold": { "option_type": "target", "value": "" },
"font_italic": { "option_type": "target", "value": "" },
"font_underline": { "option_type": "target", "value": "" },
"vertical_alignment": { "option_type": "target", "value": "" },
"horizontal_alignment": { "option_type": "target", "value": "" },
"font_case": { "option_type": "target", "value": "" }
},
"body": {
"font_height": { "option_type": "target", "value": "" },
"font_bold": { "option_type": "target", "value": "" },
"font_italic": { "option_type": "target", "value": "" },
"font_underline": { "option_type": "target", "value": "" },
"vertical_alignment": { "option_type": "target", "value": "" },
"horizontal_alignment": { "option_type": "target", "value": "" },
"font_case": { "option_type": "target", "value": "" }
}
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
| callbackId | String | The callback ID used in the request |
| modify_format_settings | Array | Array of format settings objects, one per slide |
| modify_format_settings[].title | Object | Title formatting settings for the slide |
| modify_format_settings[].body | Object | Body formatting settings for the slide |
option_type:"target"(use target template default) or"custom"(use provided value)value: Empty string for"target", or a specific value for"custom"
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callbackId query parameter is required",
"statusCode": 400
}
Callback ID Not Found (400)
{
"error": "BAD_REQUEST",
"details": "Callback ID not found or expired",
"statusCode": 400
}
Conversion Not Completed (400)
{
"error": "BAD_REQUEST",
"details": "Template conversion for the provided callbackId is not completed (status: {status}). Please wait for the conversion to complete.",
"statusCode": 400
}
Slide Images Not Found (400)
{
"error": "BAD_REQUEST",
"details": "Could not retrieve slide images from template conversion status",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Modify Format
Apply format modifications (title/body styling) to specific slides. This initiates a processing workflow and returns a new callback ID for tracking.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/modify-format
Request Body
{
"callbackId": "c927ed06-9c76-4afd-a8c2-1edb5cd84da3",
"slideIndex": [0, 1],
"selection_type": "custom",
"overall_settings_selected": false,
"modify_format_config": [
{
"title": {
"font_height": { "option_type": "custom", "value": 24 },
"font_bold": { "option_type": "custom", "value": true },
"font_italic": { "option_type": "target", "value": "" },
"font_underline": { "option_type": "target", "value": "" },
"vertical_alignment": { "option_type": "custom", "value": "top" },
"horizontal_alignment": { "option_type": "custom", "value": "left" },
"font_case": { "option_type": "target", "value": "" }
}
},
{
"body": {
"font_height": { "option_type": "custom", "value": 14 },
"font_bold": { "option_type": "target", "value": "" },
"font_italic": { "option_type": "target", "value": "" },
"font_underline": { "option_type": "target", "value": "" },
"vertical_alignment": { "option_type": "target", "value": "" },
"horizontal_alignment": { "option_type": "custom", "value": "justified" },
"font_case": { "option_type": "custom", "value": "sentenceCase" }
}
}
]
}
Required Fields:
| Field | Type | Description |
|---|---|---|
| callbackId | String | The callback ID from the start API response |
| slideIndex | Array | Array of zero-based slide indices to apply formatting to |
Optional Fields:
| Field | Type | Description |
|---|---|---|
| selection_type | String | "single", "all", or "custom". Auto-determined from slideIndex if not provided |
| overall_settings_selected | Boolean | If true, uses default target template settings and ignores modify_format_config (default: false) |
| modify_format_config | Array | Required when overall_settings_selected is false. Array length must match slideIndex length |
modify_format_config Entry:
Each entry must have either title or body (not both). Each section contains these required fields:
| Field | option_type | Valid Custom Values |
|---|---|---|
| font_height | "target" or "custom" | 8, 9, 10, 10.5, 11, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 54, 60, 72, 96 or "" |
| font_bold | "target" or "custom" | true, false, or "" |
| font_italic | "target" or "custom" | true, false, or "" |
| font_underline | "target" or "custom" | true, false, or "" |
| vertical_alignment | "target" or "custom" | "top", "middle", "bottom", or "" |
| horizontal_alignment | "target" or "custom" | "left", "center", "right", "justified", or "" |
| font_case | "target" or "custom" | "lowerCase", "upperCase", "capitaliseCase", "sentenceCase", or "" |
Note: When option_type is "target", value must be empty string "".
{
"status": "processing",
"callbackId": "new-callback-id-for-tracking"
}
Response Fields
| Field | Type | Description |
|---|---|---|
| status | String | Always processing for successful initiation |
| callbackId | String | New callback ID to use for checking status via the status API |
Next Steps:
- Use the new
callbackIdfrom the response to poll the status API until the format modification is complete:
GET /api/v1/template-converter/status/{new-callbackId}
- Once status is
completed, call the download API with the original (parent) callbackId to get the updated file:
GET /api/v1/template-converter/download/{parent-callbackId}
Invalid JSON (400)
{
"error": "BAD_REQUEST",
"details": "Invalid JSON in request body",
"statusCode": 400
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callbackId is required and must be a non-empty string",
"statusCode": 400
}
Invalid Slide Index (400)
{
"error": "BAD_REQUEST",
"details": "slideIndex must be a non-empty array",
"statusCode": 400
}
Invalid Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "Invalid or expired callbackId",
"statusCode": 400
}
Conversion Not Completed (400)
{
"error": "BAD_REQUEST",
"details": "Template conversion for the provided callbackId is not completed (status: {status}). Please wait for the conversion to complete.",
"statusCode": 400
}
Missing Config (400)
{
"error": "BAD_REQUEST",
"details": "modify_format_config is required when overall_settings_selected is false or not provided",
"statusCode": 400
}
Config Length Mismatch (400)
{
"error": "BAD_REQUEST",
"details": "modify_format_config array length must match slideIndex array length",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Template Change
Change the template for an already converted presentation.
- Request
- Success Response
- Error Response
POST /api/v1/template-converter/{callbackId}/template-change
Path Parameters
callbackId: The callback ID returned from the start API response
Request Body
{
"templateName": "abbvie_avycaz"
}
Required Fields:
templateName: The name of the new target template
{
"status": "success",
"callbackId": "new_callback_id_after_template_change",
"message": "Template change initiated successfully"
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callback_id is required",
"statusCode": 400
}
Invalid JSON (400)
{
"error": "BAD_REQUEST",
"details": "Please provide a valid JSON payload",
"statusCode": 400
}
Missing Template Name (400)
{
"error": "BAD_REQUEST",
"details": "templateName is required and must be a non-empty string",
"statusCode": 400
}
Invalid Template (400)
{
"error": "BAD_REQUEST",
"details": "Template name is invalid or not authorized for this user",
"statusCode": 400
}
Template Validation Error (400)
{
"error": "BAD_REQUEST",
"details": "Unable to validate template access at this time",
"statusCode": 400
}
Failed to Retrieve Status (400)
{
"error": "BAD_REQUEST",
"details": "Failed to retrieve template converter status",
"statusCode": 400
}
Template Conversion Not Completed (400)
{
"error": "BAD_REQUEST",
"details": "Template conversion is not completed yet. Current status: {status}",
"statusCode": 400
}
No Compliance Data Found (400)
{
"error": "BAD_REQUEST",
"details": "No compliance data found for this callback_id",
"statusCode": 400
}
Upload ID Not Found (400)
{
"error": "BAD_REQUEST",
"details": "upload_id not found in TC-API-InitialCompliance output",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Error message",
"statusCode": 500
}
Reaction Feedback
Submit user feedback and reactions for template conversions. This endpoint supports likes/dislikes and detailed textual feedback.
- Request
- Success Response
- Error Response
PUT /api/v1/template-converter/reaction-feedback
Request Body
Option 1: Like/Dislike
{
"callback_id": "36fc16ff-7d65-4b91-b011-a07cb91b6ba3",
"type": "liked",
"value": true
}
Option 2: Detailed Feedback
{
"callback_id": "36fc16ff-7d65-4b91-b011-a07cb91b6ba3",
"type": "feedback",
"value": "The template conversion was excellent!"
}
Required Fields:
callback_id: The callback ID from the conversion jobtype: Feedback type (likedfor like/dislike,feedbackfor detailed feedback)value: Boolean forlikedtype (true for like, false for dislike), or string forfeedbacktype
{
"status": "success",
"message": "Reaction updated successfully",
"callback_id": "36fc16ff-7d65-4b91-b011-a07cb91b6ba3",
"type": "liked",
"value": true
}
Note: For type: "feedback", the message will be "Feedback updated successfully".
Invalid JSON (400)
{
"error": "BAD_REQUEST",
"details": "Invalid JSON in request body",
"statusCode": 400
}
Missing Callback ID (400)
{
"error": "BAD_REQUEST",
"details": "callback_id is required",
"statusCode": 400
}
Missing Type (400)
{
"error": "BAD_REQUEST",
"details": "type is required",
"statusCode": 400
}
Missing Value (400)
{
"error": "BAD_REQUEST",
"details": "value is required",
"statusCode": 400
}
Invalid Type (400)
{
"error": "BAD_REQUEST",
"details": "type must be either \"liked\" or \"feedback\"",
"statusCode": 400
}
Invalid Value Type for Liked (400)
{
"error": "BAD_REQUEST",
"details": "value must be a boolean when type is \"liked\"",
"statusCode": 400
}
Invalid Value Type for Feedback (400)
{
"error": "BAD_REQUEST",
"details": "value must be a string when type is \"feedback\"",
"statusCode": 400
}
Invalid Callback ID (404)
{
"error": "NOT_FOUND",
"details": "Invalid callback_id or session expired",
"statusCode": 404
}
Template Conversion Not Completed (400)
{
"error": "BAD_REQUEST",
"details": "Template conversion is not completed yet. Current status: {status}",
"statusCode": 400
}
Failed to Fetch Status (400)
{
"error": "BAD_REQUEST",
"details": "Failed to fetch template converter status",
"statusCode": 400
}
No Initial Compliance Data (400)
{
"error": "BAD_REQUEST",
"details": "No initial compliance data found for this callback_id",
"statusCode": 400
}
Upload ID Not Found (400)
{
"error": "BAD_REQUEST",
"details": "upload_id not found in TC-API-InitialCompliance output",
"statusCode": 400
}
Failed to Update Feedback (400)
{
"error": "BAD_REQUEST",
"details": "Failed to update feedback in both services",
"statusCode": 400
}
Internal Server Error (500)
{
"error": "INTERNAL_SERVER_ERROR",
"details": "Failed to process reaction/feedback",
"statusCode": 500
}
Example: Complete Template Conversion Workflow
Here's a complete example of converting a presentation to a different template:
- cURL
- Node.js
- Python
# Step 1: Preprocess - Upload file (for files under 5MB, single chunk)
curl -X POST https://api.prezent.ai/api/v1/template-converter/preprocess \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileContent": "data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,{base64_encoded_content}",
"requestIdentifier": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "presentation.pptx",
"chunkIndex": 0,
"totalChunks": 1
}'
# Step 2: Finalprocess - Get file ID
curl -X POST https://api.prezent.ai/api/v1/template-converter/finalprocess \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileIdentifier": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "presentation.pptx"
}'
# Step 3: Start conversion (use fileId from Step 2)
curl -X POST https://api.prezent.ai/api/v1/template-converter/start \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fileId": "a47aacc2-a23b-4782-94d4-f6dc76f9cdca",
"templateName": "abbvie_aquipta",
"settings": {
"ai_mode": "standard",
"work_area_option": "auto-adjust"
}
}'
# Step 4: Check status (use callbackId from Step 3)
curl -X GET https://api.prezent.ai/api/v1/template-converter/status/d437b021-1ff7-4cb1-b906-69a9a8cb913e \
-H "Authorization: Bearer YOUR_API_KEY"
# Step 5: Get download URL (use callbackId from Step 3)
curl -X GET https://api.prezent.ai/api/v1/template-converter/d437b021-1ff7-4cb1-b906-69a9a8cb913e/download \
-H "Authorization: Bearer YOUR_API_KEY"
const axios = require('axios');
const fs = require('fs');
const convertTemplate = async () => {
const apiKey = 'YOUR_API_KEY';
const baseUrl = 'https://api.prezent.ai';
const headers = {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
};
try {
// Step 1: Preprocess - Upload file
const fileContent = fs.readFileSync('presentation.pptx');
const base64Content = fileContent.toString('base64');
const requestIdentifier = '550e8400-e29b-41d4-a716-446655440000';
const preprocessResponse = await axios.post(
`${baseUrl}/api/v1/template-converter/preprocess`,
{
fileContent: `data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,${base64Content}`,
requestIdentifier: requestIdentifier,
fileName: 'presentation.pptx',
chunkIndex: 0,
totalChunks: 1
},
{ headers }
);
console.log('File uploaded:', preprocessResponse.data.fileName);
// Step 2: Finalprocess - Get file ID
const finalprocessResponse = await axios.post(
`${baseUrl}/api/v1/template-converter/finalprocess`,
{
fileIdentifier: requestIdentifier,
fileName: 'presentation.pptx'
},
{ headers }
);
const fileId = finalprocessResponse.data.data.fileId;
console.log('File ID:', fileId);
// Step 3: Start conversion
const startResponse = await axios.post(
`${baseUrl}/api/v1/template-converter/start`,
{
fileId: fileId,
templateName: 'abbvie_aquipta',
settings: {
ai_mode: 'standard',
work_area_option: 'auto-adjust'
}
},
{ headers }
);
const callbackId = startResponse.data.callbackId;
console.log('Conversion started. Callback ID:', callbackId);
// Step 4: Poll for status
let status = 'processing';
while (status === 'processing') {
await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds
const statusResponse = await axios.get(
`${baseUrl}/api/v1/template-converter/status/${callbackId}`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
status = statusResponse.data.status;
console.log(`Status: ${status}`);
}
if (status === 'completed') {
// Step 5: Get download URL
const downloadResponse = await axios.get(
`${baseUrl}/api/v1/template-converter/${callbackId}/download`,
{ headers: { 'Authorization': `Bearer ${apiKey}` } }
);
console.log('Download URL:', downloadResponse.data.downloadUrl);
console.log('File name:', downloadResponse.data.fileName);
} else {
console.error('Conversion failed:', status);
}
} catch (error) {
console.error('Error:', error.response?.data || error.message);
}
};
convertTemplate();
import requests
import time
import base64
import uuid
def convert_template():
api_key = 'YOUR_API_KEY'
base_url = 'https://api.prezent.ai'
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
try:
# Step 1: Preprocess - Upload file
with open('presentation.pptx', 'rb') as f:
file_content = f.read()
base64_content = base64.b64encode(file_content).decode('utf-8')
request_identifier = str(uuid.uuid4())
preprocess_response = requests.post(
f'{base_url}/api/v1/template-converter/preprocess',
headers=headers,
json={
'fileContent': f'data:application/vnd.openxmlformats-officedocument.presentationml.presentation;base64,{base64_content}',
'requestIdentifier': request_identifier,
'fileName': 'presentation.pptx',
'chunkIndex': 0,
'totalChunks': 1
}
)
preprocess_response.raise_for_status()
print('File uploaded:', preprocess_response.json()['fileName'])
# Step 2: Finalprocess - Get file ID
finalprocess_response = requests.post(
f'{base_url}/api/v1/template-converter/finalprocess',
headers=headers,
json={
'fileIdentifier': request_identifier,
'fileName': 'presentation.pptx'
}
)
finalprocess_response.raise_for_status()
file_id = finalprocess_response.json()['data']['fileId']
print('File ID:', file_id)
# Step 3: Start conversion
start_response = requests.post(
f'{base_url}/api/v1/template-converter/start',
headers=headers,
json={
'fileId': file_id,
'templateName': 'abbvie_aquipta',
'settings': {
'ai_mode': 'standard',
'work_area_option': 'auto-adjust'
}
}
)
start_response.raise_for_status()
callback_id = start_response.json()['callbackId']
print('Conversion started. Callback ID:', callback_id)
# Step 4: Poll for status
status = 'processing'
while status == 'processing':
time.sleep(2) # Wait 2 seconds
status_response = requests.get(
f'{base_url}/api/v1/template-converter/status/{callback_id}',
headers={'Authorization': f'Bearer {api_key}'}
)
status_response.raise_for_status()
status = status_response.json()['status']
print(f'Status: {status}')
if status == 'completed':
# Step 5: Get download URL
download_response = requests.get(
f'{base_url}/api/v1/template-converter/{callback_id}/download',
headers={'Authorization': f'Bearer {api_key}'}
)
download_response.raise_for_status()
download_data = download_response.json()
print('Download URL:', download_data['downloadUrl'])
print('File name:', download_data['fileName'])
else:
print(f'Conversion failed: {status}')
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
if hasattr(e, 'response') and e.response is not None:
print(f'Response: {e.response.json()}')
convert_template()
Common Error Responses
Invalid File ID
{
"status": "failed",
"error": "INVALID_FILE_ID",
"details": "The specified file ID was not found"
}
Invalid Template Name
{
"status": "failed",
"error": "INVALID_TEMPLATE",
"details": "The specified template name is not available"
}
Callback ID Not Found
{
"status": "failed",
"error": "CALLBACK_ID_NOT_FOUND",
"details": "The specified callback ID was not found"
}
File Upload Failed
{
"status": "failed",
"error": "FILE_UPLOAD_FAILED",
"details": "File upload failed during preprocessing"
}
Rate Limit Exceeded
{
"status": "failed",
"error": "RATE_LIMIT_EXCEEDED",
"details": "Too many requests. Please try again later."
}
Best Practices
- File Upload: For files under 5MB, send as a single chunk. For larger files, split into 5MB chunks
- Polling for Status: Poll the status endpoint every 2-5 seconds while status is "processing"
- Error Handling: Always check the
statusfield and handlefailedstatus appropriately - Download URLs: Download URLs include authentication tokens and expire after a certain period. Download promptly after receiving the URL
- Request Identifier: Use a UUID format for
requestIdentifierto ensure uniqueness across uploads - File Names: Keep file names under 100 characters and avoid special characters
File Upload Script
#!/usr/bin/env python3
"""
Template Converter File Upload Tool
====================================
A standalone script to upload files for the Template Converter API.
Supports chunked uploads with retry logic, batch processing, and finalprocess.
Author: Prezent Engineering Team
Version: 1.0.0
Python: 3.6+
Quick Start:
-----------
1. Upload a file:
python3 tc_file_uploader.py --file document.pptx --api-key YOUR_KEY
2. Batch upload:
python3 tc_file_uploader.py --batch files.txt --api-key YOUR_KEY
For detailed help:
python3 tc_file_uploader.py --help
"""
import requests
import json
import base64
import os
import sys
import time
import uuid
from typing import Dict, List, Optional, Tuple
from pathlib import Path
class TemplateConverterUploader:
"""
Handles file upload with chunking for Template Converter API
"""
# File limits
MAX_FILE_SIZE_MB = 200
# Chunking configuration
CHUNK_SIZE = 5 * 1024 * 1024 # 5MB chunks
BATCH_SIZE = 5 # 5 chunks per batch
MAX_RETRIES = 3
RETRY_DELAY = 1 # seconds
# Supported file types (Template Converter specific)
SUPPORTED_EXTENSIONS = [".pptx", ".ppt"]
def __init__(self, base_url: str, api_key: str, callback_id: str = None, token: str = None, verbose: bool = True):
"""
Initialize the Template Converter uploader
Args:
base_url: Base URL of the API (e.g., 'https://api.prezent.ai')
api_key: API key for authentication
callback_id: Optional callback ID for authentication
token: Optional token for authentication
verbose: Enable verbose output
"""
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.callback_id = callback_id
self.token = token
self.verbose = verbose
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
def log(self, message: str, force: bool = False):
"""Print message if verbose mode is enabled"""
if self.verbose or force:
print(message)
def validate_file(self, file_path: str) -> Tuple[bool, Optional[str]]:
"""Validate file before upload"""
file_path_obj = Path(file_path)
if not file_path_obj.exists():
return False, f"File does not exist: {file_path}"
file_ext = file_path_obj.suffix.lower()
if file_ext not in self.SUPPORTED_EXTENSIONS:
return (
False,
f"Unsupported file type: {file_ext}. Supported: {', '.join(self.SUPPORTED_EXTENSIONS)}",
)
file_size_mb = file_path_obj.stat().st_size / (1024 * 1024)
if file_size_mb > self.MAX_FILE_SIZE_MB:
return (
False,
f"File exceeds size limit: {file_size_mb:.2f}MB > {self.MAX_FILE_SIZE_MB}MB",
)
return True, None
def read_file_as_base64(self, file_path: str) -> str:
"""Read file and convert to base64 data URL"""
with open(file_path, "rb") as file:
file_content = file.read()
base64_content = base64.b64encode(file_content).decode("utf-8")
file_ext = Path(file_path).suffix.lower()
mime_types = {
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".ppt": "application/vnd.ms-powerpoint",
}
mime_type = mime_types.get(file_ext, "application/octet-stream")
data_url = f"data:{mime_type};base64,{base64_content}"
return data_url
def create_chunks(
self, data_url: str, file_name: str, request_id: str
) -> List[Dict]:
"""Split file data into chunks"""
chunks = []
chunk_index = 0
for start in range(0, len(data_url), self.CHUNK_SIZE):
chunk_data = data_url[start : start + self.CHUNK_SIZE]
chunks.append(
{
"chunk": chunk_data,
"chunkIndex": chunk_index,
"requestIdentifier": request_id,
"filename": file_name,
}
)
chunk_index += 1
return chunks
def upload_chunk_with_retry(self, chunk: Dict, total_chunks: int) -> Dict:
"""Upload a single chunk with retry logic"""
# Build URL with query parameters if callback_id and token are provided
url = f"{self.base_url}/api/v1/template-converter/preprocess"
if self.callback_id and self.token:
url += f"?callback_id={self.callback_id}&token={self.token}"
payload = {
"fileContent": chunk["chunk"],
"requestIdentifier": chunk["requestIdentifier"],
"fileName": chunk["filename"],
"chunkIndex": chunk["chunkIndex"],
"totalChunks": total_chunks,
}
for attempt in range(self.MAX_RETRIES):
try:
response = requests.post(
url, headers=self.headers, json=payload, timeout=60
)
if response.status_code == 200:
return response.json()
elif attempt < self.MAX_RETRIES - 1:
self.log(
f" Chunk {chunk['chunkIndex']} failed with status {response.status_code} (attempt {attempt + 1}/{self.MAX_RETRIES}), retrying..."
)
time.sleep(self.RETRY_DELAY)
else:
# Parse error response
try:
error_data = response.json()
except:
error_data = response.text
return {
"error": True,
"status_code": response.status_code,
"api_response": error_data,
"chunk_index": chunk["chunkIndex"],
"url": url,
}
except Exception as e:
if attempt < self.MAX_RETRIES - 1:
self.log(
f" Chunk {chunk['chunkIndex']} exception (attempt {attempt + 1}/{self.MAX_RETRIES}), retrying..."
)
time.sleep(self.RETRY_DELAY)
else:
return {
"error": True,
"exception": str(e),
"chunk_index": chunk["chunkIndex"],
"url": url,
}
return {"error": True, "message": "Max retries exceeded"}
def upload_batch(
self, batch: List[Dict], total_chunks: int, batch_num: int
) -> List[Dict]:
"""Upload a batch of chunks"""
self.log(f" Uploading batch {batch_num} ({len(batch)} chunks)...")
responses = []
for chunk in batch:
response = self.upload_chunk_with_retry(chunk, total_chunks)
responses.append(response)
if response.get("error"):
self.log(f" ✗ Chunk {chunk['chunkIndex']} failed")
else:
self.log(f" ✓ Chunk {chunk['chunkIndex']} uploaded")
return responses
def upload_file_chunks(self, file_path: str) -> Tuple[bool, str, str, List[Dict]]:
"""Upload file in chunks - returns success, request_id, file_name, responses"""
file_name = Path(file_path).name
request_id = str(uuid.uuid4())
self.log(f"\nReading file: {file_name}")
data_url = self.read_file_as_base64(file_path)
self.log(
f"Creating chunks (chunk size: {self.CHUNK_SIZE / (1024**2):.1f}MB)..."
)
chunks = self.create_chunks(data_url, file_name, request_id)
total_chunks = len(chunks)
self.log(f"Created {total_chunks} chunks")
batches = [
chunks[i : i + self.BATCH_SIZE]
for i in range(0, len(chunks), self.BATCH_SIZE)
]
self.log(f"Created {len(batches)} batches")
self.log("\nUploading chunks...")
all_responses = []
for batch_num, batch in enumerate(batches, 1):
batch_responses = self.upload_batch(batch, total_chunks, batch_num)
all_responses.extend(batch_responses)
errors = [r for r in batch_responses if r.get("error")]
if errors:
self.log(
f"\n✗ Batch {batch_num} had {len(errors)} failed chunks", force=True
)
# Show error details
for error in errors:
self.log(
f"\n Error details for chunk {error.get('chunk_index')}:",
force=True,
)
if error.get("status_code"):
self.log(
f" HTTP Status: {error.get('status_code')}", force=True
)
if error.get("api_response"):
self.log(
f" API Response: {json.dumps(error.get('api_response'), indent=6)}",
force=True,
)
if error.get("exception"):
self.log(f" Exception: {error.get('exception')}", force=True)
return False, request_id, file_name, all_responses
self.log(f" ✓ Batch {batch_num} completed successfully")
progress = (batch_num / len(batches)) * 100
self.log(f" Progress: {progress:.1f}%")
self.log("\n✓ All chunks uploaded successfully!")
return True, request_id, file_name, all_responses
def call_finalprocess(self, file_id: str, file_name: str) -> Dict:
"""Call the finalprocess API with fileIdentifier and fileName"""
# Build URL with query parameters if callback_id and token are provided
url = f"{self.base_url}/api/v1/template-converter/finalprocess"
if self.callback_id and self.token:
url += f"?callback_id={self.callback_id}&token={self.token}"
payload = {
"fileIdentifier": file_id,
"fileName": file_name
}
self.log(f"\nCalling finalprocess API with fileIdentifier: {file_id}, fileName: {file_name}")
try:
response = requests.post(
url, headers=self.headers, json=payload, timeout=120
)
if response.status_code == 200:
finalprocess_data = response.json()
self.log("✓ Finalprocess completed")
return finalprocess_data
else:
# Parse error response
try:
error_data = response.json()
except:
error_data = response.text
return {
"error": True,
"stage": "finalprocess",
"status_code": response.status_code,
"api_response": error_data,
"url": url,
"message": f"Finalprocess API returned {response.status_code}",
}
except Exception as e:
return {
"error": True,
"stage": "finalprocess",
"exception": str(e),
"url": url,
"message": f"Finalprocess request failed: {str(e)}",
}
def upload_and_process_file(self, file_path: str) -> Dict:
"""Complete workflow: validate -> upload -> finalprocess"""
self.log("=" * 80, force=True)
self.log("TEMPLATE CONVERTER FILE UPLOAD", force=True)
self.log("=" * 80, force=True)
# Step 1: Pre-upload validation
self.log("\n[Step 1] Pre-upload validation...")
is_valid, error_msg = self.validate_file(file_path)
if not is_valid:
return {"error": True, "stage": "pre-validation", "message": error_msg}
file_size_mb = Path(file_path).stat().st_size / (1024 * 1024)
self.log(f"✓ File is valid")
self.log(f" File: {Path(file_path).name}")
self.log(f" Size: {file_size_mb:.2f}MB")
self.log(f" Type: {Path(file_path).suffix}")
# Step 2: Upload file chunks
self.log("\n[Step 2] Uploading file chunks...")
success, file_id, file_name, responses = self.upload_file_chunks(file_path)
if not success:
return {
"error": True,
"stage": "upload",
"message": "Failed to upload all chunks",
"file_id": file_id,
"responses": responses,
}
# Step 3: Call finalprocess API
self.log("\n[Step 3] Calling finalprocess API...")
finalprocess_result = self.call_finalprocess(file_id, file_name)
if finalprocess_result.get("error"):
return finalprocess_result
# Print finalprocess results
self.print_finalprocess_results(finalprocess_result, file_id)
return finalprocess_result
def print_finalprocess_results(self, finalprocess_result: Dict, file_id: str):
"""Print finalprocess results in a formatted way"""
self.log("\n" + "=" * 80, force=True)
self.log("FINALPROCESS RESULT", force=True)
self.log("=" * 80, force=True)
self.log(f"\nFile ID: {file_id}", force=True)
self.log(f"Status: {finalprocess_result.get('status', 'N/A')}", force=True)
# Print complete API response
self.log("\nCOMPLETE API RESPONSE:", force=True)
self.log("=" * 80, force=True)
self.log(json.dumps(finalprocess_result, indent=2), force=True)
self.log("=" * 80, force=True)
def print_help():
"""Print comprehensive help message"""
help_text = """
╔════════════════════════════════════════════════════════════════════════════╗
║ Template Converter File Upload Tool ║
║ Version 1.0.0 ║
╚════════════════════════════════════════════════════════════════════════════╝
DESCRIPTION:
A standalone tool to upload files for the Template Converter API.
Supports chunked uploads (5MB chunks), batch processing, automatic retries,
and finalprocess API call.
USAGE:
python3 tc_file_uploader.py [OPTIONS]
OPTIONS:
--file PATH Upload and process a PowerPoint file
--batch FILE Upload multiple files from a text file (one path per line)
--api-key KEY API authentication key (required)
--callback-id ID Callback ID for authentication (optional)
--token TOKEN Token for authentication (optional)
--url URL Base API URL (default: https://api.prezent.ai)
--quiet Suppress verbose output (only show results)
--help, -h Show this help message
EXAMPLES:
1. Upload a single PowerPoint file:
python3 tc_file_uploader.py \\
--file /path/to/presentation.pptx \\
--api-key YOUR_API_KEY
2. Upload with callback_id and token (for authenticated requests):
python3 tc_file_uploader.py \\
--file presentation.pptx \\
--api-key YOUR_KEY \\
--callback-id YOUR_CALLBACK_ID \\
--token YOUR_TOKEN
3. Batch upload from file list:
python3 tc_file_uploader.py \\
--batch file_list.txt \\
--api-key YOUR_API_KEY
(file_list.txt should contain one file path per line)
4. Upload with custom API endpoint:
python3 tc_file_uploader.py \\
--file presentation.pptx \\
--api-key YOUR_KEY \\
--url https://custom-api.example.com
5. Quiet mode (minimal output):
python3 tc_file_uploader.py \\
--file presentation.pptx \\
--api-key YOUR_KEY \\
--quiet
SUPPORTED FILE TYPES:
• PowerPoint (.pptx, .ppt)
FILE LIMITS:
• Maximum file size: 200 MB
UPLOAD DETAILS:
• Chunk size: 5 MB
• Batch size: 5 chunks per batch
• Retry attempts: 3 per chunk
• Retry delay: 1 second
WORKFLOW:
1. Pre-upload validation (file exists, type, size)
2. File chunking (5MB chunks)
3. Batch upload with retry logic (preprocess API)
4. Finalprocess API call
5. Results display
EXIT CODES:
0 - Success
1 - Error occurred
API ENDPOINTS USED:
• POST /api/v1/template-converter/preprocess
(Upload file chunks)
• POST /api/v1/template-converter/finalprocess
(Process uploaded file and return fileId)
TROUBLESHOOTING:
Error: "File does not exist"
→ Check the file path is correct and accessible
Error: "Unsupported file type"
→ Ensure file is .pptx or .ppt format
Error: "File exceeds size limit"
→ File must be under 200MB
Error: "Authentication failed"
→ Verify your API key is correct
Error: "Chunk upload failed"
→ Check network connectivity. Script automatically retries 3 times.
BATCH FILE FORMAT:
Create a text file with one file path per line:
/path/to/file1.pptx
/path/to/file2.ppt
/path/to/file3.pptx
NOTES:
• The script automatically handles chunking for large files
• Failed chunks are automatically retried (up to 3 attempts)
• Progress is displayed in real-time during upload
• Finalprocess API is called automatically after successful upload
• The fileId returned can be used for subsequent API calls
ENVIRONMENT:
• Python 3.6 or higher required
• Required package: requests (pip install requests)
SUPPORT:
For issues or questions, contact the Template Converter API team.
═══════════════════════════════════════════════════════════════════════════════
"""
print(help_text)
def process_batch_file(batch_file: str, uploader: TemplateConverterUploader) -> Dict:
"""Process multiple files from a batch file"""
if not os.path.exists(batch_file):
return {"error": True, "message": f"Batch file not found: {batch_file}"}
with open(batch_file, "r") as f:
file_paths = [line.strip() for line in f if line.strip()]
if not file_paths:
return {"error": True, "message": "Batch file is empty"}
print("=" * 80)
print(f"BATCH UPLOAD - {len(file_paths)} FILES")
print("=" * 80)
results = []
for idx, file_path in enumerate(file_paths, 1):
print(f"\n[{idx}/{len(file_paths)}] Processing: {file_path}")
print("-" * 80)
result = uploader.upload_and_process_file(file_path)
results.append({"file": file_path, "result": result})
# Print summary
print("\n" + "=" * 80)
print("BATCH UPLOAD SUMMARY")
print("=" * 80)
successful = sum(1 for r in results if not r["result"].get("error"))
failed = len(results) - successful
print(f"\nTotal Files: {len(results)}")
print(f"✓ Successful: {successful}")
print(f"✗ Failed: {failed}")
if failed > 0:
print("\nFailed Files:")
for r in results:
if r["result"].get("error"):
print(f" - {r['file']}: {r['result'].get('message')}")
return {"total": len(results), "successful": successful, "failed": failed}
def main():
"""Main function for CLI usage"""
args = sys.argv[1:]
# Show help if no arguments or --help flag
if not args or "--help" in args or "-h" in args:
print_help()
return
# Parse arguments
file_path = None
batch_file = None
api_key = None
callback_id = None
token = None
base_url = "https://api.prezent.ai"
quiet = False
i = 0
while i < len(args):
if args[i] == "--file" and i + 1 < len(args):
file_path = args[i + 1]
i += 2
elif args[i] == "--batch" and i + 1 < len(args):
batch_file = args[i + 1]
i += 2
elif args[i] == "--api-key" and i + 1 < len(args):
api_key = args[i + 1]
i += 2
elif args[i] == "--callback-id" and i + 1 < len(args):
callback_id = args[i + 1]
i += 2
elif args[i] == "--token" and i + 1 < len(args):
token = args[i + 1]
i += 2
elif args[i] == "--url" and i + 1 < len(args):
base_url = args[i + 1]
i += 2
elif args[i] == "--quiet":
quiet = True
i += 1
else:
print(f"Unknown argument: {args[i]}")
print("Use --help for usage information")
sys.exit(1)
# Validate required arguments
if not api_key:
print("✗ ERROR: --api-key is required")
print("\nUse --help for usage information")
sys.exit(1)
if not file_path and not batch_file:
print("✗ ERROR: One of --file or --batch must be provided")
print("\nUse --help for usage information")
sys.exit(1)
# Initialize uploader
uploader = TemplateConverterUploader(base_url, api_key, callback_id, token, verbose=not quiet)
# Execute workflow
try:
if batch_file:
result = process_batch_file(batch_file, uploader)
else:
result = uploader.upload_and_process_file(file_path)
# Handle result
if result.get("error"):
print("\n" + "=" * 80, file=sys.stderr)
print("❌ ERROR OCCURRED", file=sys.stderr)
print("=" * 80, file=sys.stderr)
print(f"\n✗ {result.get('message', 'Unknown error')}", file=sys.stderr)
if result.get("stage"):
print(f"\nFailed Stage: {result.get('stage')}", file=sys.stderr)
if result.get("url"):
print(f"API Endpoint: {result.get('url')}", file=sys.stderr)
if result.get("status_code"):
print(
f"\nHTTP Status Code: {result.get('status_code')}", file=sys.stderr
)
# Common status code explanations
status_explanations = {
400: "Bad Request - The request was invalid or malformed",
401: "Unauthorized - Invalid or missing API key",
403: "Forbidden - You don't have permission to access this resource",
404: "Not Found - The requested resource was not found",
429: "Too Many Requests - Rate limit exceeded",
500: "Internal Server Error - Server encountered an error",
502: "Bad Gateway - Invalid response from upstream server",
503: "Service Unavailable - Server is temporarily unavailable",
}
status_code = result.get("status_code")
if status_code in status_explanations:
print(
f"Explanation: {status_explanations[status_code]}",
file=sys.stderr,
)
if result.get("api_response"):
print("\nAPI Error Response:", file=sys.stderr)
print("-" * 80, file=sys.stderr)
try:
if isinstance(result.get("api_response"), dict):
print(
json.dumps(result.get("api_response"), indent=2),
file=sys.stderr,
)
else:
print(result.get("api_response"), file=sys.stderr)
except:
print(str(result.get("api_response")), file=sys.stderr)
print("-" * 80, file=sys.stderr)
if result.get("exception"):
print(f"\nException: {result.get('exception')}", file=sys.stderr)
if result.get("responses"):
failed_chunks = [
r for r in result.get("responses", []) if r.get("error")
]
if failed_chunks:
print(f"\nFailed Chunks: {len(failed_chunks)}", file=sys.stderr)
print("\n" + "=" * 80, file=sys.stderr)
sys.exit(1)
else:
print(f"\n✓ SUCCESS")
sys.exit(0)
except KeyboardInterrupt:
print("\n\n✗ Operation cancelled by user")
sys.exit(1)
except Exception as e:
print(f"\n✗ UNEXPECTED ERROR: {str(e)}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
Need Help?
- Visit the Getting Started Guide for authentication setup
- Check the Developer Guide for integration best practices
- Contact support at success@prezent.ai