Auto Generator API
Overview
The Auto Generator API lets you generate polished business presentations from structured data and natural language prompts. It's designed to align with your organization's templates, branding, and audience context.
Use it to:
- Automatically create decks based on user prompts
- Track generation progress using callback IDs
- Retrieve metadata like audiences for targeted presentations
- Regenerate specific slides or entire presentations
- Download completed presentations
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
POST /api/v1/autogenerator– Create a new presentationGET /api/v1/autogenerator/status– Track generation progressPOST /api/v1/autogenerator/download– Download generated presentationPOST /api/v1/autogenerator/node-change– Get node change options for a slidePOST /api/v1/autogenerator/regenerate– Regenerate slides or presentationGET /api/v1/themes– Retrieve themesPOST /api/v1/upload– Upload file for processingPOST /api/v1/preprocess– Upload file chunks for preprocessingPOST /api/v1/validate– Validate and finalize uploaded files and linksPOST /api/v1/autogenerator/reaction-feedback– Submit feedback and reactionsGET /api/v1/audiences– Retrieve target audience metadata
Auto Generator – Create Presentation
Create a new presentation generation job.
- Request
- Success Response
- Error Response
POST /api/v1/autogenerator
Request Body
{
"prompt": "string", // Required: Slide content prompt
"template_id": "string", // Optional: Slide design template ID. Takes priority over "settings.template" if both are provided.
"texts": ["string"], // Optional: Additional context text
"files": [ // Optional: Array of file objects from upload API response
{
"file_id": "string", // Required: File ID from upload response
"file_name": "string", //Required: Original file name
"file_type": "string", // Required: MIME type (e.g., "application/pdf")
"s3_path": "string", // Required: S3 storage path
"s3_bucket": "string" // Required: S3 bucket name
}
],
"web_links": ["string"], // Optional: URLs for contextual info
"audience": { // Optional: Target audience object from audience API response
"id": "string", // Audience ID (email or identifier)
"fullName": "string", // Full name of the audience
"audienceId": "string", // Unique audience identifier
"num_id": "number", // Numeric ID
"type": "string", // Type (e.g., "user", "group")
"ownerID": "string", // Owner ID (nullable)
"isActive": "boolean", // Whether audience is active
"isPrivate": "boolean", // Privacy setting
"isShared": "boolean", // Sharing status
"fingerPrint": "string", // Audience fingerprint (e.g., "Producer")
"fingerprintResponse": "array", // Fingerprint response data
"shared": "boolean" // Shared status
},
"layout_file":{"file_id":"string"}, // Optional: Layout file_id from validate API to preserve the same layout structure during generation
"settings":{
"template":"string", // Optional: Default presentation template to override account settings (e.g., "prezent_corporate_2022").
"ai_model":"string", // Optional: AI model to use for content generation (e.g., "astrid_standard", "astrid_advanced")
"image_preferences":["string"], // Optional: Preferred image sources for visual content (e.g., ["prezent_library", "company_library"])
"knowledge_base":"string", // Optional: Knowledge base scope for contextual generation (e.g., "uploaded_and_web", "uploaded")
"data_sources":["string"], // Optional: Configure how data sources are added to the presentation (e.g., ["add_sources_to_footer", "add_sources_to_slides_note"])
"preserve_text": "boolean", // Optional: Preserve original text content from prompt or add context
"speaker_notes":["string"], // Optional: Configure speaker notes generation and placement (e.g., ["add_speaker_notes","add_speaker_notes_to_slides_note"])
"voice_settings": { // Optional: Voice and tone settings for content generation
"type": "string", // Type of voice setting: "brand", "standard", or "custom"
"details": { // Required only when type is "custom"
"tone": "string", // Tone of voice (e.g., "neutral",)
"emotion": "string" // Emotional tone (e.g., "optimistic")
}
},
}
}
{
"statusCode": 200,
"body": {
"message": "Job submitted successfully",
"resData": {
"callback_id": "uuid-string",
},
"error": null
}
}
{
"statusCode": 422,
"body": {
"error": {
"code": "INVALID_INPUT",
"message": "Invalid input provided.",
"details": ["prompt is missing", "template_id is missing"]
}
}
}
Auto Generator – Check Status
Track the progress of a previously submitted presentation generation request using the callback_id.
- Request
- Response
- Error Response
GET /api/v1/autogenerator/status?callback_id={callback_id}
Query Parameters
callback_id (required): The callback ID from the generation request
{
"statusCode": 200,
"body": {
"message": "Workflow status polling completed.",
"status": "string" // "success", "inprogress", or error status
}
}
{
"statusCode": 500,
"body": {
"success": false,
"data": null,
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected server error occurred.",
"details": {
"details": "Failed to generate slides"
}
}
}
}
Auto Generator – Check Regeneration Status
Track the progress of a previously submitted regeneration request using the regeneration callback_id and the original deck's deck_callback_id.
- Request
- Response
- Error Response
GET /api/v1/autogenerator/status?callback_id={callback_id}&deck_callback_id={deck_callback_id}
Query Parameters
callback_id (required): The regeneration callback ID from the regeneration request
deck_callback_id (required): The deck callback ID of the original deck
{
"statusCode": 200,
"body": {
"message": "Workflow status polling completed.",
"status": "string" // "success", "inprogress", or error status
}
}
{
"statusCode": 500,
"body": {
"success": false,
"data": null,
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected server error occurred.",
"details": {
"details": "Failed to generate slides"
}
}
}
}
Download Generated Presentation
Download a merged presentation file.
- Request
- Success Response
- Error Response
POST /api/v1/autogenerator/download
{
"callback_id": "string", // Required: Callback ID for tracking
}
{
"statusCode": 200,
"body": {
"message": "Slides merged successfully",
"status": "success",
"output_file": "string",
"download_url": "string"
}
}
{
"statusCode": 400,
"body": {
"error": {
"code": "MISSING_SLIDES_ARRAY",
"message": "Missing or invalid slides array.",
"details": "Payload must contain a 'slides' array"
}
}
}
Get Node Change Options
Retrieve available node change options for a specific slide. This API returns node variations that can be used to change the node configuration of a slide.
- Request
- Success Response
- Error Response
POST /api/v1/autogenerator/node-change
Request Body
{
"callback_id": "string", // Required: Presentation callback ID
"slide_callback_id": "string" // Required: Slide callback ID for which to get node change options
}
{
"success": true,
"data": {
"payloads": [
{
"unique_id": "string",
"thumbnail": "string"
}
],
"total": "number"
},
"error": null
}
Response Fields
| Field | Type | Description |
|---|---|---|
| success | Boolean | Indicates if the request was successful |
| data | Object | Container for node change options |
| data.payloads | Array | Array of available node change options |
| data.payloads[].unique_id | String | Unique identifier for the node option (use this as slide value in slide_override for node_change operation) |
| data.payloads[].thumbnail | String | Thumbnail image URL for the node option |
| data.total | Number | Total number of available node change options |
| error | Null | Error object (null on success) |
Important Note: The unique_id from the response should be used as the slide value in the slide_override object when calling the regenerate API with operation=node_change.
{
"success": false,
"data": null,
"error": {
"code": "string",
"message": "string",
"details": "string"
}
}
Regenerate Presentation
Regenerate specific slides or entire presentations with new parameters.
- Request
- Success Response
- Error Response
POST /api/v1/autogenerator/regenerate?operation={operation}
Query Parameters
- `operation` (required): Type of regeneration operation
Supported Operations:
- `audience_change` - Change target audience
- `template_change` - Change presentation template
- `slide_change` - Change specific slides
- `context_change` - Change context for slides
- `smart_chart_change` - Change smart chart configurations
- `node_change` - Change node configurations
- `story_content_change` - Change story content
- `add_slide` - Add a new slide to the presentation
- `duration_change` - Change presentation duration
- `slides_footer_change` - Update data sources footer settings
- `voice_change` - Change voice and tone settings
**Request Body**
**audience_change:**
```json
{
"callback_id": "string", // Required: Original callback ID
"audience": { // Required: Audience object
"id": "string", // Email or identifier (e.g., "user@example.com")
"num_id": "number", // Numeric ID
"type": "string", // Type: "user" or "group"
"ownerID": "string", // Owner ID (can be empty string)
"email": "string", // Email address
"fingerPrint": "string", // Fingerprint (e.g., "Surgeon", "Producer")
"fingerprintResponse": "array" // Fingerprint response data
}
}
template_change:
{
"callback_id": "string", // Required: Original callback ID
"template_code": "string" // Required: Template code to switch to
}
slide_change:
{
"callback_id": "string", // Required: Original callback ID
"slide_override": { // Required: Slide override configuration
"workflow": "string", // slide callback ID
"slide": "string", // Slide source identifier
"ui_slide_position": "number", // Slide position (0-based index)
"ui_total_slides": "number" // Total number of slides in presentation
}
}
node_change:
{
"callback_id": "string", // Required: Original callback ID
"slide_override": { // Required: Node configuration
"workflow": "string", // slide callback ID
"node_count": "number", // Number of nodes
"ui_slide_position": "number", // Slide position (0-based index)
"ui_total_slides": "number", // Total number of slides
"unique_id": "string" // Slide source identifier
}
}
smart_chart_change:
{
"callback_id": "string", // Required: Original callback ID
"slide_override": { // Required: Chart configuration
"workflow": "string", // slide callback ID
"slide_type": "string", // Chart type (e.g., "stacked_column", "bar", "line")
"ui_slide_position": "number", // Slide position (0-based index)
"ui_total_slides": "number" // Total number of slides
}
}
context_change:
{
"callback_id": "string", // Required: Original callback ID
"context": { // Required: Context information
"context": "string", // Context text/prompt
"extract_graph_data": "boolean", // Whether to extract graph data
"externalContext": "boolean", // Whether to use external context
"files": "array", // Array of file objects
"web_links": "array", // Array of web links
"texts": "array" // Array of text strings
},
"slide_override": { // Required: Slide configuration
"workflow": "string", // slide callback ID
"ui_slide_position": "number", // Slide position (0-based index)
"ui_total_slides": "number" // Total number of slides
}
}
add_slide:
{
"callback_id": "string", // Required: Original callback ID
"context": { // Required: New slide context
"context": "string", // Slide content prompt
"extract_graph_data": "boolean", // Whether to extract graph data
"externalContext": "boolean", // Whether to use external context
"files": "array", // Array of file objects
"web_links": "array", // Array of web links
"texts": "array" // Array of text strings
},
"slide_position": "number" // Required: Position to insert new slide
}
duration_change:
{
"callback_id": "string", // Required: Original callback ID
"duration": { // Required: Duration settings
"no_of_slides": "number" // Number of slides (1 slide = 3 min)
}
}
slides_footer_change:
{
"callback_id": "string", // Required: Original callback ID
"data_sources_settings": { // Required: Data sources settings
"add_sources_to_footer": "boolean", // Add sources to footer
"add_sources_to_slides_note": "boolean" // Add sources to slide notes
}
}
voice_change:
{
"callback_id": "string", // Required: Original callback ID
"voice_settings": { // Required: Voice settings
"type": "string", // Voice type: "standard", "brand", or "custom"
"details": { // Required only when type is "custom"
"tone": "string", // Tone of voice (e.g., "neutral", "formal")
"emotion": "string" // Emotional tone (e.g., "optimistic", "confident")
}
}
}
story_content_change:
{
"callback_id": "string", // Required: Original callback ID
"story_content_override": [ // Required: Array of story content objects
{
"title": "string", // Main title of the slide
"subtitle": "string", // Subtitle (can be null)
"content": [ // Array of content strings
"string"
],
"section_header": [ // Array of section headers
"string"
],
"hub_title": "string", // Hub title (can be empty string)
"slide_type": "string", // Slide type (e.g., "normal")
"ref_id": "string", // Reference ID
"workflow": "string", // Slide callback ID
"ui_slide_position": "number", // Slide position (0-based index)
"ui_total_slides": "number" // Total number of slides
}
]
}
{
"statusCode": 200,
"body": {
"message": "Regeneration job submitted successfully",
"resData": {
"callback_id": "uuid-string",
"original_callback_id": "uuid-string"
},
"error": null
}
}
{
"statusCode": 422,
"body": {
"error": {
"code": "INVALID_INPUT",
"message": "Invalid input provided.",
"details": "callback_id is required, operation is required"
}
}
}
Retrieve Themes
Retrieve themes associated with the company.
- Request
- Success Response
- Error Response
GET /api/v1/autogenerator/templates
Query Parameters
All query parameters are optional:
| Parameter | Type | Description |
|---|---|---|
| sort | String | Sort order for themes by name. Valid values: name:asc (ascending) or name:desc (descending) |
| name | String | Search for templates by name |
{
"statusCode": 200,
"body": {
"status": "success",
"log": "success",
"data": [
{
"code": "string",
"name": "string"
}
]
}
}
{
"statusCode": 500,
"body": {
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected server error occurred.",
"details": "Error retrieving themes"
}
}
}
Upload File
Upload a file for processing.
- Request
- Success Response
- Error Response
POST /api/v1/upload
{
"fileContent": "string", // Required: Base64 encoded file content
"fileName": "string" // Required: Name of the file with extension
}
Supported File Types: pptx, docx, txt, pdf, xls, xlsx, csv
{
"statusCode": 200,
"body": {
"message": "File uploaded successfully",
"data": {
"requestId": "uuid-string",
"fileName": "string",
"source": "generate",
"uploadDetails": {}
}
}
}
{
"statusCode": 400,
"body": {
"error": {
"code": "MISSING_FILE_CONTENT",
"message": "Missing file content or file name.",
"details": "fileContent and fileName are 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.
- Request
- Success Response
- Error Responses
POST /api/v1/preprocess
Request Body
{
"fileContent": "string", // Required: Base64-encoded file content
"fileName": "string", // Required: Name of the file including extension (max 100 characters)
"chunkIndex": "number", // Required: Zero-based index of the current chunk (>= 0)
"totalChunks": "number", // Required: Total number of chunks for this file (> 0)
"requestIdentifier": "string" // Required: Unique identifier for this upload request (UUID format)
}
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, docx, pdf, txt, xlsx, csv
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 validate API.
A unique 5-character code is prepended to the filename (e.g., A1B2C_quarterly-report.pdf).
{
"message": "File received successfully",
"requestIdentifier": "string", // The unique identifier used for this upload
"fileName": "string" // Final filename with 5-digit unique code prepended (e.g., "A1B2C_quarterly-report.pdf")
}
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 - Special Characters (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "Special characters not allowed in filename"
}
Invalid Input - Filename Length (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "Filename length must be between 1 and 100 characters"
}
Invalid Input - Multiple Extensions (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "Multiple file extensions are not allowed"
}
Invalid Input - SQL Injection (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "SQL injection attempt detected"
}
Invalid Input - HTML Injection (400)
{
"status": "failed",
"error": "INVALID_INPUT",
"details": "HTML injection attempt detected"
}
File Upload Failed (400)
{
"status": "failed",
"error": "FILE_UPLOAD_FAILED",
"details": "File upload failed during preprocessing",
"error": "..."
}
Bad Request (400)
{
"status": "failed",
"error": "BAD_REQUEST",
"details": "File upload failed"
}
Validate File
Validates and finalizes uploaded files and web links. This API processes file identifiers (from previous preprocess operations), completes the file upload process, validates file integrity, and returns detailed file information including S3 paths. It also supports web link validation.
- Request
- Success Response
- Typical Workflow
POST /api/v1/validate
Request Body
Option 1: Validate Files Only
{
"fileIdentifiers": { // Object mapping request identifiers to file names
"string": "string" // "requestIdentifier": "fileName.ext"
}
}
Option 2: Validate Web Links Only
{
"webLinks": ["string"] // Array of web URLs to validate
}
Option 3: Validate Both Files and Web Links
{
"fileIdentifiers": { // Optional: Object mapping request identifiers to file names
"string": "string"
},
"webLinks": ["string"] // Optional: Array of web URLs to validate
}
Field Validation Rules
| Field | Type | Validation Rules |
|---|---|---|
| fileIdentifiers | Object | Optional, must be an object if provided, at least one of fileIdentifiers or webLinks is required |
| webLinks | Array | Optional, must be an array if provided, at least one of fileIdentifiers or webLinks is required |
Notes:
At least one of fileIdentifiers or webLinks must be provided.
Can validate multiple files and links in a single request.
File identifiers should use the requestIdentifier from the preprocess API response.
File names should be the original file names (without the prepended code).
{
"message": "string",
"data": {
"files": [
{
"file_id": "string",
"file_size": "number",
"file_type": "string",
"is_corrupted": "boolean",
"is_password_protected": "boolean",
"log": "string",
"number_of_pages": "number",
"s3_bucket": "string",
"s3_path": "string",
"status": "string",
"unique_id": "string"
}
],
"text_input": [],
"web_links": [
{
"status": "string",
"web_url": "string"
}
]
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
| message | String | Status message indicating validation result |
| data | Object | Validation results container |
| data.files | Array | Array of file validation results |
| data.files[].file_id | String | Unique identifier for the file (request identifier) |
| data.files[].file_size | Number | File size in MB |
| data.files[].file_type | String | MIME type of the file |
| data.files[].status | String | Validation status: "success" or "failed" |
| data.files[].log | String | Status log message |
| data.files[].is_corrupted | Boolean | Whether the file is corrupted |
| data.files[].is_password_protected | Boolean | Whether the file is password protected |
| data.files[].number_of_pages | Number | Number of pages in the file |
| data.files[].s3_path | String | S3 path where the file is stored (null if failed) |
| data.files[].s3_bucket | String | S3 bucket name (null if failed) |
| data.files[].unique_id | String | Unique identifier for the file |
| data.text_input | Array | Array of text input validation results (currently empty) |
| data.web_links | Array | Array of web link validation results |
| data.web_links[].status | String | Validation status: "success" or "failed" |
| data.web_links[].web_url | String | Web URL that was validated |
| data.web_links[].log | String | Status log message (present when status is "failed") |
Failed File Response
{
"message": "string",
"data": {
"files": [
{
"file_id": "string",
"file_name": "string",
"status": "string",
"log": "string"
}
],
"text_input": [],
"web_links": [
{
"status": "string",
"log": "string",
"web_url": "string"
}
]
}
}
Note: When a file validation fails, the response will only include file_id, file_name, status, and log fields. Fields like s3_path, s3_bucket, file_size, file_type, is_corrupted, is_password_protected, number_of_pages, and unique_id are not included in failed responses.
Supported MIME Types
| File Extension | MIME Type |
|---|---|
| pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
| ppt | application/vnd.ms-powerpoint |
| docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
| application/pdf | |
| txt | text/plain |
| xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| csv | text/csv |
Step 1: Preprocess File
Upload file chunks using /api/v1/preprocess:
POST /api/v1/preprocess
{
"fileContent": "data:application/pdf;base64,...",
"fileName": "report.pdf",
"chunkIndex": 0,
"totalChunks": 1,
"requestIdentifier": "550e8400-e29b-41d4-a716-446655440000"
}
Response:
{
"message": "File received successfully",
"requestIdentifier": "550e8400-e29b-41d4-a716-446655440000",
"fileName": "A1B2C_report.pdf"
}
Step 2: Validate File
Finalize and validate using /api/v1/validate:
POST /api/v1/validate
{
"fileIdentifiers": {
"550e8400-e29b-41d4-a716-446655440000": "report.pdf"
},
"webLinks": [
"https://example.com/article"
]
}
Response:
{
"message": "string",
"data": {
"files": [
{
"file_id": "string",
"file_size": "number",
"file_type": "string",
"is_corrupted": "boolean",
"is_password_protected": "boolean",
"log": "string",
"number_of_pages": "number",
"s3_bucket": "string",
"s3_path": "string",
"status": "string",
"unique_id": "string"
}
],
"text_input": [],
"web_links": [
{
"status": "string",
"web_url": "string"
}
]
}
}
Important Notes:
For files < 5MB: Send as a single chunk.
For files > 5MB: Split into 5MB chunks and send parallel requests with the same requestIdentifier.
Wait for all chunk uploads to succeed before calling validate API.
Validate API can process multiple files and links in a single request.
Reaction Feedback
Submit user feedback and reactions for generated presentations. This endpoint supports likes/dislikes and detailed textual feedback.
- Request
- Success Response
- Error Responses
POST /api/v1/autogenerator/reaction-feedback
Request Body
Option 1: Like
{
"uuid": "string", // Required: Presentation callback ID
"type": "liked", // Required: Feedback type
"value": "boolean" // Required: true for like, false for dislike
}
Option 2: Feedback
{
"uuid": "string", // Required: Presentation callback ID
"type": "feedback", // Required: Feedback type
"value": "string", // Required: Detailed feedback text
"shareDetails": "boolean" // Required: Whether to share feedback details
}
{
"message": "string", // Success message indicating the operation type (e.g., "liked operation completed successfully")
"status": "success", // Always "success" for successful operations
"uuid": "string", // Echo of the callback ID from the request
"type": "string", // Echo of the type from the request ("liked" or "feedback")
"value": "boolean/string" // Echo of the value from the request
}
Response Fields
| Field | Type | Description |
|---|---|---|
| message | String | Success message indicating the operation type |
| status | String | Always "success" for successful operations |
| uuid | String | Echo of the UUID from the request |
| type | String | Echo of the type from the request |
| value | Boolean/String | Echo of the value from the request |
Invalid JSON (400)
{
"error": "INVALID_JSON",
"message": "Please provide a valid JSON payload",
"statusCode": 400
}
Invalid Payload (400)
{
"error": "INVALID_PAYLOAD",
"message": "Payload must be a valid object",
"statusCode": 400
}
Missing Required Field (400)
{
"error": "MISSING_REQUIRED_FIELD",
"message": "Payload must contain 'uuid' field",
"statusCode": 400
}
Retrieve Audiences
Use this to fetch a list of audiences in your organization (used in generation context).
- Request
- Success Response
- Error Response
GET /api/v1/audiences
Query Parameters
id (optional): Search for specific audience by ID
sort (optional): Sort order for results
{
"statusCode": 200,
"body": {
"result": [
{
"id": "string",
"fingerPrint": "string"
}
]
}
}
{
"statusCode": 500,
"body": {
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected server error occurred.",
"details": "error message"
}
}
}
Common Error Responses
Unauthorized Access
{
"statusCode": 401,
"body": {
"error": {
"code": "UNAUTHORIZED",
"message": "Unauthorized access."
}
}
}
Method Not Allowed
{
"statusCode": 405,
"body": {
"error": {
"code": "METHOD_NOT_ALLOWED",
"message": "HTTP method not allowed."
}
}
}
Endpoint Not Found
{
"statusCode": 404,
"body": {
"error": {
"code": "ENDPOINT_NOT_FOUND",
"message": "API path not found."
}
}
}
Invalid Input
{
"statusCode": 422,
"body": {
"error": {
"code": "INVALID_INPUT",
"message": "Invalid input provided.",
"details": ["error details"]
}
}
}
Internal Server Error
{
"statusCode": 500,
"body": {
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected server error occurred.",
"details": "error message"
}
}
}
Sample Test Script
#!/usr/bin/env python3
"""
Test script for the complete /api/v1/autogenerator workflow
Tests the full process: generate -> poll status -> download presentation
"""
import requests
import json
import base64
import os
import time
from typing import Dict, List, Optional
class AutogeneratorAPITester:
def __init__(self, base_url: str, api_key: str):
"""
Initialize the API tester
Args:
base_url: Base URL of the API (e.g., 'https://api.prezent.ai')
api_key: API key for authentication
"""
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
def test_basic_autogenerator(self, prompt: str, template_id: str) -> Dict:
"""
Test autogenerator API with minimal required parameters
Args:
prompt: The slide content prompt (required)
template_id: The slide design template ID (required)
Returns:
API response as dictionary
"""
payload = {
"prompt": prompt,
"template_id": template_id
}
return self._make_request(payload)
def poll_status(self, callback_id: str, max_attempts: int = 150) -> Dict:
"""
Poll the status endpoint every 2 seconds until status is "success"
Args:
callback_id: The callback ID received from the generate call
max_attempts: Maximum number of polling attempts (default: 150 = 5 minutes)
Returns:
Final status response or error
"""
url = f"{self.base_url}/api/v1/autogenerator/status"
print(f"Polling status for callback ID: {callback_id}")
attempt = 1
while attempt <= max_attempts:
try:
response = requests.get(url, headers=self.headers, params={"callback_id": callback_id}, timeout=30)
if response.status_code == 200:
status_data = response.json()
current_status = status_data.get('resData', {}).get('status', 'unknown')
if current_status == "success":
print(f"Status: {current_status}")
return status_data
elif current_status == "failed":
print(f"Status: {current_status}")
return status_data
elif current_status in ["pending", "processing", "in_progress"]:
time.sleep(2)
else:
time.sleep(2)
else:
time.sleep(2)
except Exception as e:
time.sleep(2)
attempt += 1
return {"error": True, "message": "Max polling attempts reached"}
def download_presentation(self, callback_id: str) -> Dict:
"""
Call the download endpoint to get the final download URL
Args:
callback_id: The callback ID received from the generate call
Returns:
Download response with download_url
"""
url = f"{self.base_url}/api/v1/autogenerator/download"
payload = {
"callback_id": callback_id
}
try:
response = requests.post(url, headers=self.headers, json=payload, timeout=30)
if response.status_code == 200:
download_data = response.json()
return download_data
else:
return {"error": True, "status_code": response.status_code, "response": response.text}
except Exception as e:
return {"error": True, "exception": str(e)}
def complete_autogenerator_workflow(self, prompt: str, template_id: str) -> Dict:
"""
Complete autogenerator workflow: generate -> poll status -> download
Args:
prompt: The slide content prompt
template_id: The slide design template ID
Returns:
Final result with download URL
"""
print(f"Starting autogenerator workflow...")
# Step 1: Generate presentation
generate_result = self.test_basic_autogenerator(prompt, template_id)
if generate_result.get("error"):
return generate_result
# Extract callback ID
callback_id = generate_result.get('resData', {}).get('callback_id')
if not callback_id:
return {"error": True, "message": "No callback ID received"}
print(f"Generation initiated. Callback ID: {callback_id}")
# Step 2: Poll status until completion
status_result = self.poll_status(callback_id)
if status_result.get("error"):
return status_result
# Step 3: Download presentation
download_result = self.download_presentation(callback_id)
if download_result.get("error"):
return download_result
print("Workflow completed successfully!")
return download_result
def _make_request(self, payload: Dict) -> Dict:
"""
Make the actual HTTP request to the API
Args:
payload: Request payload
Returns:
API response as dictionary
"""
url = f"{self.base_url}/api/v1/autogenerator"
try:
response = requests.post(url, headers=self.headers, json=payload, timeout=30)
if response.status_code == 200:
response_data = response.json()
return response_data
else:
return {"error": True, "status_code": response.status_code, "response": response.text}
except Exception as e:
return {"error": True, "exception": str(e)}
def main():
"""Main test function"""
# Configuration - Update these values
BASE_URL = "https://api.prezent.ai" # Replace with your actual API domain
API_KEY = "your-api-key-here" # Replace with your actual API key
# Test data
test_prompt = "Create a 2 slide presentation about artificial intelligence and its applications in modern business"
test_template_id = "template_123" # Replace with actual template ID
# Initialize tester
tester = AutogeneratorAPITester(BASE_URL, API_KEY)
# Test complete workflow
result = tester.complete_autogenerator_workflow(test_prompt, test_template_id)
if not result.get("error"):
download_url = result.get('resData', {}).get('download_url', 'N/A')
print(f"Download URL: {download_url}")
else:
print(f"Error: {result.get('message', 'Unknown error')}")
if __name__ == "__main__":
# Check if configuration is set
if "api.prezent.ai" in BASE_URL or "your-api-key-here" in API_KEY:
print("Please update the BASE_URL and API_KEY variables in the script before running!")
print(" BASE_URL: Your API domain (e.g., 'https://api.prezent.ai')")
print(" API_KEY: Your authentication API key")
exit(1)
main()
File Upload & Validation Script
#!/usr/bin/env python3
"""
AutoGenerator File Upload & Validation Tool
============================================
A standalone script to upload and validate files for the AutoGenerator API.
Supports chunked uploads with retry logic, batch processing, and validation.
Author: Prezent Engineering Team
Version: 1.0.0
Python: 3.6+
Quick Start:
-----------
1. Upload a file:
python3 autogen_file_uploader.py --file document.pdf --api-key YOUR_KEY
2. Validate a link:
python3 autogen_file_uploader.py --link https://example.com/doc --api-key YOUR_KEY
3. Batch upload:
python3 autogen_file_uploader.py --batch files.txt --api-key YOUR_KEY
For detailed help:
python3 autogen_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 FileUploadTester:
"""
Handles file upload with chunking and validation for AutoGenerator API
"""
# File limits
MAX_FILE_SIZE_MB = 200
MAX_PAGES = 100
# 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
SUPPORTED_EXTENSIONS = [".pptx", ".docx", ".txt", ".pdf", ".xls", ".xlsx", ".csv"]
def __init__(self, base_url: str, api_key: str, verbose: bool = True):
"""
Initialize the file upload tester
Args:
base_url: Base URL of the API (e.g., 'https://api.prezent.ai')
api_key: API key for authentication
verbose: Enable verbose output
"""
self.base_url = base_url.rstrip("/")
self.api_key = api_key
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",
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".pdf": "application/pdf",
".txt": "text/plain",
".xls": "application/vnd.ms-excel",
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".csv": "text/csv",
}
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"""
url = f"{self.base_url}/api/v1/preprocess"
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, List[Dict]]:
"""Upload file in chunks"""
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, 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, all_responses
def validate_uploaded_file(self, request_id: str, file_name: str) -> Dict:
"""Validate the uploaded file"""
url = f"{self.base_url}/api/v1/validate"
payload = {"fileIdentifiers": {request_id: file_name}}
self.log(f"\nValidating file...")
try:
response = requests.post(
url, headers=self.headers, json=payload, timeout=60
)
if response.status_code == 200:
validation_data = response.json()
self.log("✓ Validation completed")
return validation_data
else:
# Parse error response
try:
error_data = response.json()
except:
error_data = response.text
return {
"error": True,
"stage": "validation",
"status_code": response.status_code,
"api_response": error_data,
"url": url,
"message": f"Validation API returned {response.status_code}",
}
except Exception as e:
return {
"error": True,
"stage": "validation",
"exception": str(e),
"url": url,
"message": f"Validation request failed: {str(e)}",
}
def validate_link(self, web_url: str) -> Dict:
"""Validate a web link"""
self.log("=" * 80, force=True)
self.log("LINK VALIDATION", force=True)
self.log("=" * 80, force=True)
url = f"{self.base_url}/api/v1/validate"
payload = {"webLinks": [web_url]}
self.log(f"\nValidating link: {web_url}")
try:
response = requests.post(
url, headers=self.headers, json=payload, timeout=60
)
if response.status_code == 200:
validation_data = response.json()
self.log("✓ Link validation completed")
# Print validation results
self.print_validation_results(validation_data)
return validation_data
else:
# Parse error response
try:
error_data = response.json()
except:
error_data = response.text
return {
"error": True,
"stage": "link_validation",
"status_code": response.status_code,
"api_response": error_data,
"url": url,
"message": f"Link validation API returned {response.status_code}",
}
except Exception as e:
return {
"error": True,
"stage": "link_validation",
"exception": str(e),
"url": url,
"message": f"Link validation request failed: {str(e)}",
}
def upload_and_validate_file(self, file_path: str) -> Dict:
"""Complete workflow: validate -> upload -> validate"""
self.log("=" * 80, force=True)
self.log("FILE UPLOAD AND VALIDATION", 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, request_id, responses = self.upload_file_chunks(file_path)
if not success:
return {
"error": True,
"stage": "upload",
"message": "Failed to upload all chunks",
"request_id": request_id,
"responses": responses,
}
# Step 3: Validate uploaded file
self.log("\n[Step 3] Validating uploaded file...")
validation_result = self.validate_uploaded_file(
request_id, Path(file_path).name
)
if validation_result.get("error"):
return validation_result
# Print validation results
self.print_validation_results(validation_result)
return validation_result
def print_validation_results(self, validation_result: Dict):
"""Print validation results in a formatted way"""
self.log("\n" + "=" * 80, force=True)
self.log("VALIDATION RESULT", force=True)
self.log("=" * 80, force=True)
files_data = validation_result.get("data", {}).get("files", [])
if files_data:
for file_data in files_data:
self.log(f"\nFile ID: {file_data.get('file_id')}", force=True)
self.log(f"Status: {file_data.get('status')}", force=True)
self.log(
f"Number of Pages: {file_data.get('number_of_pages', 'N/A')}",
force=True,
)
self.log(f"Page Size: {file_data.get('page_size', 'N/A')}", force=True)
self.log(
f"Is Accessible: {file_data.get('is_accessible', 'N/A')}",
force=True,
)
self.log(
f"Is Password Protected: {file_data.get('is_password_protected', 'N/A')}",
force=True,
)
self.log(
f"Is Corrupted: {file_data.get('is_corrupted', 'N/A')}", force=True
)
if file_data.get("status") != "success":
self.log(f"\n⚠ Validation Issues:", force=True)
if file_data.get("number_of_pages", 0) > self.MAX_PAGES:
self.log(
f" - File exceeds page limit ({file_data.get('number_of_pages')} > {self.MAX_PAGES})",
force=True,
)
if file_data.get("is_password_protected"):
self.log(f" - File is password protected", force=True)
if file_data.get("is_corrupted"):
self.log(f" - File is corrupted", force=True)
if not file_data.get("is_accessible"):
self.log(f" - File is not accessible", force=True)
web_links = validation_result.get("data", {}).get("web_links", [])
if web_links:
for link_data in web_links:
self.log(f"\nURL: {link_data.get('web_url')}", force=True)
self.log(f"Status: {link_data.get('status')}", force=True)
self.log(
f"Is Healthy: {link_data.get('is_healthy', 'N/A')}", force=True
)
self.log(
f"Is Scrappable: {link_data.get('is_scrappable', 'N/A')}",
force=True,
)
self.log(
f"Is GDrive Link: {link_data.get('is_gdrive_link', 'N/A')}",
force=True,
)
self.log(
f"Is Accessible: {link_data.get('is_accessible', 'N/A')}",
force=True,
)
self.log("\n" + "=" * 80, force=True)
# Print complete API response
self.log("\nCOMPLETE API RESPONSE:", force=True)
self.log("=" * 80, force=True)
self.log(json.dumps(validation_result, indent=2), force=True)
self.log("=" * 80, force=True)
def print_help():
"""Print comprehensive help message"""
help_text = """
╔════════════════════════════════════════════════════════════════════════════╗
║ AutoGenerator File Upload & Validation Tool ║
║ Version 1.0.0 ║
╚════════════════════════════════════════════════════════════════════════════╝
DESCRIPTION:
A standalone tool to upload and validate files for the AutoGenerator API.
Supports chunked uploads (5MB chunks), batch processing, automatic retries,
and comprehensive validation.
USAGE:
python3 autogen_file_uploader.py [OPTIONS]
OPTIONS:
--file PATH Upload and validate a file
--link URL Validate a web link (Google Drive, URLs, etc.)
--batch FILE Upload multiple files from a text file (one path per line)
--api-key KEY API authentication key (required)
--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 file:
python3 autogen_file_uploader.py \\
--file /path/to/document.pdf \\
--api-key YOUR_API_KEY
2. Validate a Google Drive link:
python3 autogen_file_uploader.py \\
--link "https://docs.google.com/document/d/YOUR_DOC_ID" \\
--api-key YOUR_API_KEY
3. Batch upload from file list:
python3 autogen_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 autogen_file_uploader.py \\
--file document.docx \\
--api-key YOUR_KEY \\
--url https://custom-api.example.com
5. Quiet mode (minimal output):
python3 autogen_file_uploader.py \\
--file document.pdf \\
--api-key YOUR_KEY \\
--quiet
SUPPORTED FILE TYPES:
• PowerPoint (.pptx)
• Word Documents (.docx)
• PDF Files (.pdf)
• Text Files (.txt)
• Excel (.xls, .xlsx)
• CSV (.csv)
FILE LIMITS:
• Maximum file size: 200 MB
• Maximum pages: 100 pages
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
4. Post-upload validation (pages, corruption, password)
5. Results display
EXIT CODES:
0 - Success
1 - Error occurred
API ENDPOINTS USED:
• POST /api/v1/preprocess
(Upload file chunks)
• POST /api/v1/validate
(Validate uploaded files or links)
TROUBLESHOOTING:
Error: "File does not exist"
→ Check the file path is correct and accessible
Error: "Unsupported file type"
→ Ensure file extension is supported (see SUPPORTED FILE TYPES)
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.
Error: "File is password protected"
→ Remove password protection before uploading
Error: "File is corrupted"
→ File may be damaged, try re-exporting
BATCH FILE FORMAT:
Create a text file with one file path per line:
/path/to/file1.pdf
/path/to/file2.docx
/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
• Validation checks file integrity, page count, and accessibility
ENVIRONMENT:
• Python 3.6 or higher required
• Required package: requests (pip install requests)
SUPPORT:
For issues or questions, contact the AutoGenerator API team.
═══════════════════════════════════════════════════════════════════════════════
"""
print(help_text)
def process_batch_file(batch_file: str, tester: FileUploadTester) -> 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 = tester.upload_and_validate_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
web_link = None
batch_file = None
api_key = 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] == "--link" and i + 1 < len(args):
web_link = 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] == "--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 web_link and not batch_file:
print("✗ ERROR: One of --file, --link, or --batch must be provided")
print("\nUse --help for usage information")
sys.exit(1)
# Initialize tester
tester = FileUploadTester(base_url, api_key, verbose=not quiet)
# Execute workflow
try:
if batch_file:
result = process_batch_file(batch_file, tester)
elif file_path:
result = tester.upload_and_validate_file(file_path)
else:
result = tester.validate_link(web_link)
# 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()