Use this server-to-server API to convert a PDF or slide image into a PowerPoint file. For full-element conversion, submit {"mode":"full_element_editable"}. The result contains editable text and separately selectable visual elements, usually transparent images. Native PowerPoint vectors, chart data and animations are not reconstructed automatically.
Base URL: https://2pptx.com/api/v1/partner
Contact support@2pptx.com to agree on pricing, settlement, page allowance, expected volume and support arrangements, and to obtain a dedicated API key with full-element access. Website checkout prices do not define your API agreement. This pilot API has no formal uptime or processing-time SLA.
Before you start
- Keep the API key on your server. Send
Authorization: Bearer <partner_api_key>to the API endpoints below. - Use the complete key saved at creation. The Admin list's
key_prefixidentifies a key but cannot authenticate requests; the complete key is displayed only once. - Both
2pptx_test_...and2pptx_live_...keys run real production conversions and use the assigned allowance. There is no simulated/free sandbox. - Input: one PDF, PNG, JPG or JPEG file per job, from 1 to 36,700,160 bytes (35 MiB). PPTX input and multi-image batch upload are not supported by this API. Combine images into a PDF before uploading if you need one multi-page result.
- Full-element access and a positive monthly full-element page allowance must be enabled for your account. The default per-job cap is 100 pages; your account may have a different cap. Images count as one page; PDF pages are counted by the server.
- Read the data processing note. Full-element processing involves image-model providers, Canva and, when needed, authorized operators.
Quick start with the Python example
Download partner_full_element.py. It initializes a job, uploads the file, submits full-element conversion, polls status, downloads the result and checks the PPTX ZIP structure and page count.
Install its HTTP dependency:
python -m pip install httpx
Set PARTNER_API_KEY through your server's environment or secret manager. Then run:
python partner_full_element.py --file sample.pdf --idempotency-key customer-order-001 --output result.pptx
Choose a new idempotency key for each new conversion. For retries, keep the key and the file unchanged. The example also uses this value as external_job_id; do not change the filename or metadata when resuming through initialization.
The example prints partner_job_id. To resume an already submitted job without uploading again:
python partner_full_element.py --job-id pj_your_job_id --output result.pptx
Polling stops after two hours by default; this is a client timeout, not a delivery estimate or a cancellation. Use --max-wait 14400 to wait longer. API calls retry transport failures, HTTP 429 and 5xx up to five attempts, retaining the same request. If a file transfer fails, rerun the same request before expiry or resume an already submitted job by ID. The example never creates a replacement order automatically and refuses to overwrite an existing output file.
Open the downloaded PPTX to review text, layout and element separation. OCR can omit or misrecognize characters; check names, numbers and page labels against the source before using the result. A valid ZIP and matching page count do not establish visual quality.
1. Initialize an upload
POST /jobs/init-upload
Authorization: Bearer <partner_api_key>
Idempotency-Key: customer-order-001
Content-Type: application/json
{
"file_name": "sample.pdf",
"file_type": "pdf",
"file_size": 123456,
"external_job_id": "customer-order-001"
}
| Field | Required | Meaning |
|---|---|---|
file_name |
Yes | Nonblank original filename. |
file_type |
Yes | pdf, png, jpg or jpeg. |
file_size |
Yes | Exact positive integer byte count, at most 36,700,160. |
external_job_id |
No | Your reference, returned in status responses. It does not replace Idempotency-Key. |
Unknown JSON fields are rejected. Do not include mode in initialization; choose it at submission. Page quotas are checked at submission, so successful initialization does not mean you have enough quota to convert the file.
HTTP 200:
{
"partner_job_id": "pj_example",
"external_job_id": "customer-order-001",
"status": "awaiting_upload",
"upload": {
"method": "PUT",
"url": "https://storage.example/signed-upload-url",
"expires_in": 1800
}
}
Idempotency keys are scoped to your account. Repeating the same key and metadata returns the same job; changing the metadata returns 409 idempotency_conflict. The key is not a file-content checksum: never reuse it for different file contents, even if their sizes match.
Only awaiting_upload jobs receive an upload URL. A replay after submission returns identifiers and status without upload. Reinitialization does not extend the original upload deadline. An expired upload requires a new request/key; an active submitted job should be queried rather than replaced.
2. Upload the file bytes
Send the binary file to the exact upload.url, including its query string:
curl --fail-with-body --request PUT "$UPLOAD_URL" \
--header "Content-Type: application/octet-stream" \
--data-binary @sample.pdf
UPLOAD_URL is the URL returned by initialization. Do not send a multipart form, base64 JSON, or the Partner API Authorization header to storage. The signed URL is itself a temporary credential; keep it out of logs and public pages. Upload and download hosts may differ from the API host.
The upload URL normally lasts 30 minutes. Finish both uploading and submitting within that window. Check the upload response before submitting. The API checks the object size, then locks a copy for processing; later writes to the original URL cannot alter an accepted job.
3. Submit full-element conversion
POST /jobs/pj_example/submit
Authorization: Bearer <partner_api_key>
Content-Type: application/json
{"mode":"full_element_editable"}
HTTP 200 returns the job ID and its current status, which may already be processing:
{"partner_job_id":"pj_example","status":"queued"}
The server reserves the actual page count atomically. Insufficient allowance or an excessive page count does not create a production order. Full-element conversion uses a fixed option bundle: omit original_quality_export, ocr_textbox_merge_beta and remove_notebooklm_watermark. On the first submission, explicitly enabling the first two or disabling watermark removal returns invalid_options.
PNG and JPEG sources are decoded before reserving pages. Corrupt images or content that does not match file_type return 400 invalid_source_file without creating an order or reserving allowance. An unreadable PDF returns page_count_failed.
Repeat submission with the same effective options after a timeout or uncertain response. Both ordinary and full-element replays return the original job's status, including processing and terminal states; they do not restart it or reserve pages again. Changed options return 409 submit_options_conflict. Revoking a feature grant prevents new submissions but still allows accepted jobs to be replayed with their original options. Disabling the entire account prevents all Partner API requests; contact support in that case. An upload that expired before its first submission cannot be resumed this way.
For ordinary text-editing conversion, the optional mode defaults to auto. Its optional booleans are remove_notebooklm_watermark (default true), original_quality_export and ocr_textbox_merge_beta (both default false, subject to account permissions and feature quotas). Always send the explicit full-element mode when that is the desired output. Unknown fields, including a misspelled model, are rejected with 422 invalid_request.
4. Poll the job
GET /jobs/pj_example
Authorization: Bearer <partner_api_key>
Example during processing:
{
"partner_job_id": "pj_example",
"external_job_id": "customer-order-001",
"status": "processing",
"file_name": "sample.pdf",
"file_type": "pdf",
"file_size": 123456,
"page_count": 3,
"created_at": "2026-10-12T01:00:00+00:00",
"submitted_at": "2026-10-12T01:01:00+00:00",
"completed_at": null,
"expires_at": "2026-10-13T01:01:00+00:00",
"error": null,
"mode": "full_element_editable",
"phase": "preprocessing",
"quota_status": "reserved"
}
Timestamps are ISO 8601 with a timezone offset. Poll every 3–5 seconds. Use the top-level status to decide whether the job is terminal:
| Status | Action |
|---|---|
awaiting_upload |
Finish upload and submit within the upload deadline. |
queued |
Wait for shared production capacity. |
processing |
Continue polling; recovery may require operator intervention. |
completed |
Request and save the final PPTX. |
failed |
Stop polling and contact support with the job ID and error. |
cancelled |
The order was terminated; it cannot be restarted by resubmitting. |
expired |
Upload, processing or result availability has expired. |
Full-element jobs expose mode, phase and quota_status after successful submission. Before that, these fields are absent and page_count is zero. Processing phases are:
| Phase | Meaning |
|---|---|
waiting_capacity |
Waiting for admission; top-level status is queued. |
preprocessing |
Automatic image and text preprocessing. |
preprocess_failed |
Preprocessing needs operator recovery. |
awaiting_manual |
Waiting for a Runner or operator. |
manual_processing |
Runner/operator is producing the result. |
manual_recovery / manual_failed |
Production needs recovery. |
manual_review |
Awaiting review. |
completed |
Final PPTX delivered. |
failed / expired |
Processing terminated or expired. |
Recovery phases keep status=processing and the reservation. Do not create a replacement job just because a phase contains failed. Contact support if progress stalls. Intermediate preprocessing files are not delivered through the Partner download endpoint.
expires_at describes the current processing deadline before delivery. On delivery it becomes the result-access deadline, normally 24 hours after completion. It is not an estimated completion time; always use the value returned by the API.
5. Download the result
GET /jobs/pj_example/download
Authorization: Bearer <partner_api_key>
HTTP 200:
{"download_url":"https://storage.example/signed-download-url","expires_in":3600}
Then download the actual file, without the Partner Authorization header:
curl --fail-with-body "$DOWNLOAD_URL" --output result.pptx
This endpoint returns JSON, not PPTX bytes. A signed link normally lasts one hour. If it expires while the result is still available, request a fresh link for the same job. Save your own copy promptly; the download endpoint stops issuing links once the job expires. Result access expiry is separate from physical storage deletion and third-party retention; see the data processing note.
Allowance, queueing and billing
- Full-element allowance is separate from ordinary and enhancement quotas. Exhausted ordinary quota does not block full-element uploads or submissions.
- Submission sets
quota_status=reserved. Final delivery changes it tosettled. Termination or processing expiry releases an unsettled reservation (released). Result download expiry does not refund delivered pages. - Replays and replacement delivery do not bill pages twice. Undelivered reservations carry across month boundaries. Settled pages count in the delivery month, using the application's calendar month; the default timezone is
Asia/Shanghai(UTC+08:00), not a rolling 30-day window. - Full-element allowance is finite: zero disallows new full-element submissions. Ordinary quota may be unlimited when unset; zero disallows ordinary submissions.
- Ordinary, HD and textbox-merge usage counts delivered pages in the delivery month. Result download expiry does not remove these pages from quotas or usage reports. Jobs that fail, are cancelled or expire before delivery do not consume completed-page allowance.
- Default limits are 10 job creations and 10 submissions per minute per account, 20 queued jobs per account, one active full-element job per account and one active full-element job across all Partners. Account limits may differ, but increasing an account limit does not override the global limit or shared website capacity.
- Accepted jobs can wait in the shared production queue. API submission does not open a payment checkout. API pricing, payment and reconciliation follow your agreed account terms.
There are currently no public Partner endpoints for account balance/usage, job listing, cancellation, or webhooks. Store the returned job IDs, poll for updates, and ask support for usage, allowance changes or termination. Send your partner_job_id and external_job_id, never your API key.
Errors and retries
Application-generated Partner API errors use this envelope:
{"error":{"code":"full_element_quota_exceeded","message":"Full-element monthly quota is insufficient, including reserved pages."}}
Validation errors use HTTP 422 and include error.details, a list of field, message and type entries. Parse code, not English message text. Storage services and reverse proxies can return a different body, including XML or HTML; handle non-JSON HTTP failures as well.
| HTTP | Code | Response |
|---|---|---|
| 400 | idempotency_key_required |
Supply a stable Idempotency-Key for initialization. |
| 400 | unsupported_file_type, file_too_large |
Correct the file type or size. |
| 400 | invalid_options, page_count_failed |
Correct the options or provide a readable, unencrypted PDF. |
| 400 | invalid_source_file |
Provide a readable PNG or JPEG whose content matches file_type; no pages were reserved. |
| 400/403 | option_not_enabled |
Use a supported mode/feature and obtain permission. |
| 401 | invalid_api_key |
Check the key and exact Bearer header; do not keep retrying. |
| 403 | partner_disabled |
Contact support. |
| 403 | full_element_page_limit_exceeded |
Reduce page count or agree on a higher per-job cap. |
| 403 | full_element_quota_exceeded |
Wait for reserved pages to be released or request more allowance. |
| 403 | quota_exceeded, original_quality_quota_exceeded, textbox_merge_quota_exceeded |
Ordinary/feature quota is exhausted. |
| 404 | job_not_found |
Check the ID and account; other accounts' jobs are not accessible. |
| 404/405 | not_found, method_not_allowed |
Correct the URL or HTTP method. |
| 409 | idempotency_conflict, submit_options_conflict |
Restore the original request; do not silently change an accepted job. |
| 409 | upload_not_found |
Ensure PUT succeeded and the byte count matches initialization; retry the same job while its upload window is open. |
| 409 | upload_expired |
Create a new upload with a new key only after confirming the old upload expired. |
| 409 | job_not_completed |
Continue polling before requesting a download. |
| 409/410 | job_expired |
Job cannot be submitted/downloaded in its current state; query it before deciding on a replacement. |
| 422 | invalid_request |
Correct required fields, values, JSON syntax or unknown fields. |
| 429 | rate_limited, too_many_queued_jobs |
Respect Retry-After (normally 60 seconds), then retry the same request. |
| 503 | storage_unavailable |
Respect Retry-After (normally 5 seconds), then retry the same request. |
| 500 | internal_error |
Retry with bounded backoff and the same identifiers; contact support if persistent. |
| Other 4xx | http_error |
Correct the request as described by the message. |
For transport timeouts or proxy 502/504 responses, the outcome may be uncertain. Retry initialization with the same key and metadata, or query/re-submit the same job with the same full-element options. Never generate a new key just because a response was lost. Use bounded retries with increasing delays; production clients should add jitter. Retry a failed binary download by obtaining a fresh signed URL while the result is still available.