Skip to main content

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 templates
  • POST /api/v1/template-converter/preprocess – Upload file chunks for preprocessing
  • POST /api/v1/template-converter/finalprocess – Finalize file upload and get file ID
  • POST /api/v1/template-converter/start – Convert presentation to a different template
  • GET /api/v1/template-converter/status/{callbackId} – Get conversion job status
  • GET /api/v1/template-converter/download/{callbackId} – Download converted template
  • GET /api/v1/template-converter/{callbackId}/review-suggestions – Get review suggestions for converted template
  • PATCH /api/v1/template-converter/{callbackId}/review-suggestions – Update review suggestions
  • GET /api/v1/template-converter/work-area-options – Get work area adjustment options for a slide
  • POST /api/v1/template-converter/adjust-work-area – Apply work area adjustment to a slide
  • GET /api/v1/template-converter/layouts – Get available layouts for slides
  • POST /api/v1/template-converter/update-layout – Apply a new layout to slides
  • GET /api/v1/template-converter/modify-format-settings – Get modify format settings for slides
  • POST /api/v1/template-converter/modify-format – Apply format modifications to slides
  • POST /api/v1/template-converter/{callbackId}/template-change – Change template for converted presentation
  • PUT /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.

GET /api/v1/template-converter/templates

Query Parameters

  • No query parameters required

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.

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

FieldTypeValidation Rules
fileContentStringRequired, must not be empty, must include data URL format
fileNameStringRequired, 1-100 characters, no special characters, no multiple extensions
chunkIndexNumberRequired, must be >= 0
totalChunksNumberRequired, must be > 0
requestIdentifierStringRequired, 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.

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.

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 response
  • fileName: The original file name (without the prepended unique code)

Start Template Conversion

Convert a presentation to a different template using the file ID obtained from the finalprocess API.

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 response
  • templateName: 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 process
    • settings.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 setting
      • settings.modifyFormat.title: String - Use "source" to keep original title formatting or "target" to apply target template formatting (default: "target")

Get Conversion Status

Check the status of a template conversion job using the callback ID returned from the start API.

GET /api/v1/template-converter/status/{callbackId}

Path Parameters

  • callbackId: The callback ID returned from the start API response

Download Converted Template

Get the download URL for the converted template file.

GET /api/v1/template-converter/download/{callbackId}

Path Parameters

  • callbackId: The callback ID returned from the start API response

Get Review Suggestions

Retrieve review suggestions for color and special color changes in the converted template.

GET /api/v1/template-converter/{callbackId}/review-suggestions

Path Parameters

  • callbackId: The callback ID returned from the start API response

Update Review Suggestions

Update review suggestions by accepting, rejecting, or editing color suggestions.

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 colorSuggestions or specialColorSuggestions must 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: The suggestedColor from 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: The suggestedColor from 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.

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.

GET /api/v1/template-converter/work-area-options

Query Parameters

ParameterTypeRequiredDescription
callbackIdStringYesThe callback ID from the start API response
slideIndexNumberYesThe zero-based index of the slide to get options for

Adjust Work Area

Apply a work area adjustment to a specific slide. This updates how content fits within the slide layout.

POST /api/v1/template-converter/adjust-work-area

Request Body

{
"callbackId": "01977761-5b38-4c00-afb6-c3015dc25e30",
"slideIndex": 0,
"selection": "auto-adjust"
}

Required Fields:

FieldTypeDescription
callbackIdStringThe callback ID from the start API response
slideIndexNumberThe zero-based index of the slide to adjust
selectionStringThe work area adjustment to apply

Valid Selection Values:

  • no-adjust - Keep original layout without adjustments
  • auto-adjust - Auto adjustment preserving aspect ratio
  • scale-to-fit - Scale content to fit the work area
  • auto-adjust-la - Auto adjustment with left alignment

Get Layouts

Retrieve available layouts for applying to slides. Returns both target template layouts and input deck layouts (if available).

GET /api/v1/template-converter/layouts

Query Parameters

ParameterTypeRequiredDescription
callbackIdStringYesThe callback ID from the start API response
slideIndexNumberYesThe zero-based index of the slide to get layouts for

Update Layout

Apply a new layout to one or more slides. This initiates a processing workflow and returns a new callback ID for tracking.

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:

FieldTypeDescription
callbackIdStringThe callback ID from the start API response
slideIndexArrayArray of zero-based slide indices to apply the layout to (must have at least one element)
layoutIdStringThe layout ID from the get-layouts API response
sourceStringSource of the layout: targetTemplateLayouts or inputDeckLayouts

Optional Fields:

FieldTypeDescription
selection_typeStringHow the slides were selected. If not provided, automatically determined based on slideIndex

Source Values:

  • targetTemplateLayouts - Use a layout from the target template
  • inputDeckLayouts - Use a layout from the original input deck

Selection Type (auto-determined if not provided):

  • single - If slideIndex has exactly one element
  • Apply to all slides - If slideIndex covers all slides in the presentation
  • Apply to custom slides - For any other selection

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.

GET /api/v1/template-converter/modify-format-settings

Query Parameters

ParameterTypeRequiredDescription
callbackIdStringYesThe callback ID from the start API response

Modify Format

Apply format modifications (title/body styling) to specific slides. This initiates a processing workflow and returns a new callback ID for tracking.

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:

FieldTypeDescription
callbackIdStringThe callback ID from the start API response
slideIndexArrayArray of zero-based slide indices to apply formatting to

Optional Fields:

FieldTypeDescription
selection_typeString"single", "all", or "custom". Auto-determined from slideIndex if not provided
overall_settings_selectedBooleanIf true, uses default target template settings and ignores modify_format_config (default: false)
modify_format_configArrayRequired 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:

Fieldoption_typeValid 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 "".

Template Change

Change the template for an already converted presentation.

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

Reaction Feedback

Submit user feedback and reactions for template conversions. This endpoint supports likes/dislikes and detailed textual feedback.

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 job
  • type: Feedback type (liked for like/dislike, feedback for detailed feedback)
  • value: Boolean for liked type (true for like, false for dislike), or string for feedback type

Example: Complete Template Conversion Workflow

Here's a complete example of converting a presentation to a different template:

# 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"

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

  1. File Upload: For files under 5MB, send as a single chunk. For larger files, split into 5MB chunks
  2. Polling for Status: Poll the status endpoint every 2-5 seconds while status is "processing"
  3. Error Handling: Always check the status field and handle failed status appropriately
  4. Download URLs: Download URLs include authentication tokens and expire after a certain period. Download promptly after receiving the URL
  5. Request Identifier: Use a UUID format for requestIdentifier to ensure uniqueness across uploads
  6. 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?