Slideform REST API

Programmatic access to projects, jobs, files, and usage data.

Authentication

All endpoints (except /health) require an API key in the x-api-key header.

API keys are scoped to an organization. Generate one from the Slideform app under Admin → API.

// Every request must include this header
x-api-key: ta_<your_api_key>

Typical Workflow

  1. Check quotaGET /usage to verify remaining slides
  2. List projectsGET /projects to find the project ID
  3. Get project detailsGET /project?project=<id> to discover pragmas
  4. Submit a jobPOST /job with pragma values
  5. Poll for completionGET /status/<job_id> until status is "ok"
  6. Download the fileGET /job/<job_id>/file
  7. Clean upDELETE /job/<job_id>/file to remove files you no longer need
  8. Review historyGET /project/<id>/downloads to see past jobs

Code Examples

List Projects

Fetch all projects in your organization.

curl -s -H "x-api-key: ta_your_key" \
  https://rest.slideform.co/projects
import requests

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"
HEADERS = {"x-api-key": API_KEY}

resp = requests.get(f"{BASE}/projects", headers=HEADERS)
for p in resp.json()["projects"]:
    print(f"{p['id']}  {p['name']}")
const API_KEY = "ta_your_key";
const BASE    = "https://rest.slideform.co";
const headers = { "x-api-key": API_KEY };

const resp = await fetch(`${BASE}/projects`, { headers });
const data = await resp.json();

data.projects.forEach(p => console.log(p.id, p.name));
require "net/http"
require "json"

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"

uri = URI("#{BASE}/projects")
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = API_KEY

resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)

data["projects"].each { |p| puts "#{p['id']}  #{p['name']}" }

Full Workflow: Submit, Poll, Download

End-to-end example that submits a job, polls until completion, and downloads the result.

#!/bin/bash
API_KEY="ta_your_key"
BASE="https://rest.slideform.co"

# 1. Submit a job
JOB=$(curl -s -X POST "$BASE/job" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "abc123",
    "pragmas": {
      "{{report_date}}": "2026-03-01",
      "{{region}}": "North America"
    }
  }')

JOB_ID=$(echo "$JOB" | jq -r '.id')
echo "Submitted job: $JOB_ID"

# 2. Poll for completion
while true; do
  STATUS=$(curl -s -H "x-api-key: $API_KEY" "$BASE/status/$JOB_ID")
  JOB_STATUS=$(echo "$STATUS" | jq -r '.status')

  if [ "$JOB_STATUS" = "ok" ]; then
    echo "Job complete!"
    break
  elif [ "$JOB_STATUS" = "failed" ]; then
    echo "Job failed."
    exit 1
  fi

  echo "Status: $JOB_STATUS — waiting 5s..."
  sleep 5
done

# 3. Download the file
FILE_NAME=$(echo "$STATUS" | jq -r '.file_name')
curl -s -H "x-api-key: $API_KEY" \
  "$BASE/job/$JOB_ID/file" \
  -o "$FILE_NAME"

echo "Downloaded: $FILE_NAME"
import time
import requests

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"
HEADERS = {"x-api-key": API_KEY}

# 1. Submit a job
job = requests.post(
    f"{BASE}/job",
    headers=HEADERS,
    json={
        "project": "abc123",
        "pragmas": {
            "{{report_date}}": "2026-03-01",
            "{{region}}": "North America",
        },
    },
).json()

job_id = job["id"]
print(f"Submitted job: {job_id}")

# 2. Poll for completion
while True:
    status = requests.get(
        f"{BASE}/status/{job_id}", headers=HEADERS
    ).json()

    if status["status"] == "ok":
        print("Job complete!")
        break
    elif status["status"] == "failed":
        raise RuntimeError("Job failed")

    print(f"Status: {status['status']} — waiting 5s...")
    time.sleep(5)

# 3. Download the file
file_name = status["file_name"]
resp = requests.get(
    f"{BASE}/job/{job_id}/file", headers=HEADERS
)

with open(file_name, "wb") as f:
    f.write(resp.content)

print(f"Downloaded: {file_name}")
import { writeFile } from "node:fs/promises";

const API_KEY = "ta_your_key";
const BASE    = "https://rest.slideform.co";
const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" };

// 1. Submit a job
const jobResp = await fetch(`${BASE}/job`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    project: "abc123",
    pragmas: {
      "{{report_date}}": "2026-03-01",
      "{{region}}": "North America",
    },
  }),
});
const job = await jobResp.json();
const jobId = job.id;
console.log(`Submitted job: ${jobId}`);

// 2. Poll for completion
let status;
while (true) {
  const r = await fetch(`${BASE}/status/${jobId}`, { headers });
  status = await r.json();

  if (status.status === "ok") {
    console.log("Job complete!");
    break;
  } else if (status.status === "failed") {
    throw new Error("Job failed");
  }

  console.log(`Status: ${status.status} — waiting 5s...`);
  await new Promise(r => setTimeout(r, 5000));
}

// 3. Download the file
const fileResp = await fetch(`${BASE}/job/${jobId}/file`, { headers });
const buffer = Buffer.from(await fileResp.arrayBuffer());
await writeFile(status.file_name, buffer);
console.log(`Downloaded: ${status.file_name}`);
require "net/http"
require "json"

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"

def api_get(path)
  uri = URI("#{BASE}#{path}")
  req = Net::HTTP::Get.new(uri)
  req["x-api-key"] = API_KEY
  Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
end

def api_post(path, body)
  uri = URI("#{BASE}#{path}")
  req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "x-api-key" => API_KEY)
  req.body = body.to_json
  Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
end

# 1. Submit a job
resp = api_post("/job", {
  project: "abc123",
  pragmas: {
    "{{report_date}}" => "2026-03-01",
    "{{region}}"     => "North America",
  }
})
job = JSON.parse(resp.body)
job_id = job["id"]
puts "Submitted job: #{job_id}"

# 2. Poll for completion
status = nil
loop do
  status = JSON.parse(api_get("/status/#{job_id}").body)

  case status["status"]
  when "ok"
    puts "Job complete!"
    break
  when "failed"
    abort "Job failed"
  end

  puts "Status: #{status['status']} — waiting 5s..."
  sleep 5
end

# 3. Download the file
file_resp = api_get("/job/#{job_id}/file")
file_name = status["file_name"]
File.binwrite(file_name, file_resp.body)
puts "Downloaded: #{file_name}"

Upload a File and Use in a Job

Upload an image, then reference it in a job submission.

# 1. Upload the image
curl -s -X POST "$BASE/files" \
  -H "x-api-key: $API_KEY" \
  -F "file=@/path/to/logo.png"

# 2. Submit a job referencing the uploaded image
curl -s -X POST "$BASE/job" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "abc123",
    "pragmas": {
      "{{report_date}}": "2026-03-01",
      "{{logo}}": "logo.png"
    },
    "options": {
      "{{logo}}": { "type": "image", "crop": true }
    }
  }'
import requests

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"
HEADERS = {"x-api-key": API_KEY}

# 1. Upload the image
with open("logo.png", "rb") as f:
    upload = requests.post(
        f"{BASE}/files",
        headers={"x-api-key": API_KEY},
        files={"file": f},
    ).json()

print(f"Uploaded: {upload['id']}")

# 2. Submit a job referencing the uploaded image
job = requests.post(
    f"{BASE}/job",
    headers=HEADERS,
    json={
        "project": "abc123",
        "pragmas": {
            "{{report_date}}": "2026-03-01",
            "{{logo}}": "logo.png",
        },
        "options": {
            "{{logo}}": {"type": "image", "crop": True},
        },
    },
).json()

print(f"Job submitted: {job['id']}")
import { readFile } from "node:fs/promises";

const API_KEY = "ta_your_key";
const BASE    = "https://rest.slideform.co";

// 1. Upload the image
const form = new FormData();
form.append("file", new Blob([await readFile("logo.png")]), "logo.png");

const upload = await fetch(`${BASE}/files`, {
  method: "POST",
  headers: { "x-api-key": API_KEY },
  body: form,
});
const uploadData = await upload.json();
console.log(`Uploaded: ${uploadData.id}`);

// 2. Submit a job referencing the uploaded image
const job = await fetch(`${BASE}/job`, {
  method: "POST",
  headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({
    project: "abc123",
    pragmas: {
      "{{report_date}}": "2026-03-01",
      "{{logo}}": "logo.png",
    },
    options: {
      "{{logo}}": { type: "image", crop: true },
    },
  }),
});
const jobData = await job.json();
console.log(`Job submitted: ${jobData.id}`);
require "net/http"
require "json"

API_KEY = "ta_your_key"
BASE    = "https://rest.slideform.co"

# 1. Upload the image
uri = URI("#{BASE}/files")
file = File.open("logo.png", "rb")
form = [["file", file, { filename: "logo.png" }]]
req = Net::HTTP::Post.new(uri)
req["x-api-key"] = API_KEY
req.set_form(form, "multipart/form-data")

resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
upload = JSON.parse(resp.body)
puts "Uploaded: #{upload['id']}"

# 2. Submit a job referencing the uploaded image
uri = URI("#{BASE}/job")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "x-api-key" => API_KEY)
req.body = {
  project: "abc123",
  pragmas: {
    "{{report_date}}" => "2026-03-01",
    "{{logo}}"     => "logo.png",
  },
  options: {
    "{{logo}}" => { type: "image", crop: true },
  }
}.to_json

resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
job = JSON.parse(resp.body)
puts "Job submitted: #{job['id']}"

Send an Email

Send an email with HTML content to a list of recipients.

curl -s -X POST "$BASE/email/send" \
  -H "x-api-key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Monthly Report</h1><p>Attached.</p>",
    "subject": "March 2026 Report",
    "recipients": ["team@example.com"]
  }'
resp = requests.post(
    f"{BASE}/email/send",
    headers=HEADERS,
    json={
        "html": "<h1>Monthly Report</h1><p>Attached.</p>",
        "subject": "March 2026 Report",
        "recipients": ["team@example.com"],
    },
).json()

print(resp["message"])  # "Email sent to 1 recipient(s)"
const resp = await fetch(`${BASE}/email/send`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    html: "<h1>Monthly Report</h1><p>Attached.</p>",
    subject: "March 2026 Report",
    recipients: ["team@example.com"],
  }),
});
const result = await resp.json();
console.log(result.message); // "Email sent to 1 recipient(s)"
resp = api_post("/email/send", {
  html:       "<h1>Monthly Report</h1><p>Attached.</p>",
  subject:    "March 2026 Report",
  recipients: ["team@example.com"],
})
data = JSON.parse(resp.body)
puts data["message"]  # "Email sent to 1 recipient(s)"

Error Responses

All errors return a consistent JSON body:

{
  "status": "failed",
  "message": "Optional error description"
}
HTTP CodeMeaning
200Success (or job still processing — check the status field)
400Bad request — missing or invalid parameters
401Unauthorized — invalid or missing API key
404Resource not found
500Internal server error

Projects

List Projects

GET /projects

Returns all projects accessible to the authenticated organization.

Parameters

None.

Response

{
  "projects": [
    { "id": "abc123", "name": "Monthly Report" },
    { "id": "def456", "name": "Quarterly Review" }
  ]
}
FieldTypeDescription
projectsarrayList of project objects
projects[].idstringUnique project identifier
projects[].namestringDisplay name

Get Project Details

GET /project?project=<project_id>

Returns project metadata and the list of pragmas (template placeholders). Use this to discover what values to pass when submitting a job.

Query Parameters

ParameterRequiredDescription
projectRequiredThe project ID

Response

{
  "id": "abc123",
  "name": "Monthly Report",
  "description": "Auto-generated monthly sales report",
  "owner": "user@example.com",
  "pragmas": [
    { "pragma": "{{report_date}}", "type": "text" },
    { "pragma": "{{region}}", "type": "dropdown" },
    { "pragma": "{{logo}}", "type": "image" }
  ]
}
FieldTypeDescription
idstringProject ID
namestringProject name
descriptionstringProject description
ownerstringEmail of the project owner
pragmasarrayTemplate placeholders
pragmas[].pragmastringPragma key including {{ delimiters
pragmas[].typestringtext, dropdown, image, chart, table, etc.

Jobs

Submit a Job

POST /job

Submits a form to generate an output file from a project template. The job runs asynchronously — use the returned id to poll for completion.

Request Body

FieldRequiredTypeDescription
project Required string The project ID
pragmas Required object Key-value mapping of pragma keys to values. Keys must include {{ and }} delimiters.
options Optional object Per-pragma options, keyed by pragma name. Only needed for image pragmas.

Example Request

{
  "project": "abc123",
  "pragmas": {
    "{{report_date}}": "2026-03-01",
    "{{region}}": "North America"
  }
}

Example with Image Options

{
  "project": "abc123",
  "pragmas": {
    "{{report_date}}": "2026-03-01",
    "{{logo}}": "https://example.com/logo.png"
  },
  "options": {
    "{{logo}}": {
      "type": "image",
      "crop": true,
      "anchor": "center",
      "transparent": false
    }
  }
}

Image Option Fields

FieldTypeDefaultDescription
typestringSet to "image"
cropbooleanfalseCrop image to fit placeholder
anchorstring"center"Anchor position within placeholder
transparentbooleanfalsePreserve transparency

Response

{
  "status": "ok",
  "id": "a1b2c3d4e5f6",
  "file_name": "Monthly_Report_03-15-2026_01-30-00_f7e8d9.pptx",
  "project": "abc123",
  "ts": 1710504600.0,
  "duplicate": false
}
FieldTypeDescription
statusstring"ok" on successful submission
idstringUnique job ID — use with /status/<job_id> and /job/<job_id>/file
file_namestringGenerated output file name
projectstringProject ID
tsnumberUnix timestamp of submission
duplicatebooleantrue if an identical pending job was reused
Pragmas not included in the request use their project-configured default values. For dropdown/multidropdown pragmas, omitting the value uses the default selection.

Submit a Bulk Job

POST /bulk

Submits multiple jobs from a single project. Can generate separate output files per pragma set, or combine all into a single document.

Request Body

FieldRequiredTypeDescription
project Required string The project ID
pragmas Required array Array of pragma value objects, one per output section
settings Optional object Bulk operation settings
mode Optional string "single" to combine all into one document. Omit for separate files.

Example (separate files)

{
  "project": "abc123",
  "pragmas": [
    { "{{region}}": "North America", "{{quarter}}": "Q1" },
    { "{{region}}": "Europe", "{{quarter}}": "Q1" }
  ]
}

Example (single combined document)

{
  "project": "abc123",
  "pragmas": [
    { "{{region}}": "North America", "{{quarter}}": "Q1" },
    { "{{region}}": "Europe", "{{quarter}}": "Q1" }
  ],
  "mode": "single"
}

Response

{
  "status": "ok",
  "results": {
    "id": "d4e5f6a1b2c3",
    "project": "abc123",
    "status": "submitted",
    "file_name": "Monthly_Report_03-15-2026_a1b2c3.pptx",
    "ts": 1710504600.0
  },
  "message": "Check for results in the Slideform app"
}
In "single" mode, all pragma sets are merged into one document with a single job ID. Without "single", separate jobs are submitted for each pragma set.

Check Job Status

GET /status/<job_id>

Polls the status of a submitted job. Call repeatedly until status is "ok" (completed) or "failed".

Path Parameters

ParameterRequiredDescription
job_idRequiredThe job ID from POST /job or POST /bulk

Response (in progress)

{
  "status": "submitted"
}

Response (completed)

{
  "status": "ok",
  "id": "a1b2c3d4e5f6",
  "file_name": "Monthly_Report_03-15-2026_01-30-00_f7e8d9.pptx",
  "file_format": "pptx",
  "url": "https://storage.example.com/...",
  "project": "abc123",
  "ts": 1710504600.0
}
FieldTypeDescription
statusstring"submitted", "processing", "ok", or "failed"
idstringJob ID (completed only)
file_namestringOutput file name (completed only)
file_formatstringFormat: pptx, docx, xlsx, pdf, html (completed only)
urlstringDownload URL if cloud storage is configured (may be null)
projectstringProject ID (completed only)
tsnumberUnix timestamp of completion (completed only)

Get Job History

GET /project/<project_id>/downloads?limit=10

Returns past jobs for a specific project, sorted by most recent first.

Path Parameters

ParameterRequiredDescription
project_idRequiredThe project ID

Query Parameters

ParameterRequiredDefaultDescription
limitOptional10Max results to return

Response

{
  "downloads": [
    {
      "id": "a1b2c3d4e5f6",
      "project": "abc123",
      "status": "completed",
      "url": "https://storage.example.com/...",
      "file_name": "Monthly_Report_03-15-2026_f7e8d9.pptx",
      "ts": 1710504600.0
    }
  ],
  "project_id": "abc123"
}
Admin users see all downloads across the organization. Non-admin users only see their own. Preview and PNG-format jobs are excluded.

Files

Download File by Name

GET /file/<file_name>

Downloads a generated output file by its file name. If the job is still processing, returns the current status instead.

Path Parameters

ParameterRequiredDescription
file_nameRequiredThe output file name

Response (completed)

Binary file contents with appropriate Content-Type and Content-Disposition headers.

Response (still processing)

{
  "status": "submitted"
}

Download File by Job ID

GET /job/<job_id>/file

Downloads the output file for a specific job. This is the recommended download method since it uses the stable job ID.

Path Parameters

ParameterRequiredDescription
job_idRequiredThe job ID

Response (completed)

Binary file contents with appropriate Content-Type and Content-Disposition headers.

Response (still processing)

{
  "status": "submitted"
}

Delete File by Job ID

DELETE /job/<job_id>/file

Deletes the output file and storage record for a completed job. The file is permanently removed and cannot be recovered.

Path Parameters

ParameterRequiredDescription
job_idRequiredThe job ID

Response (200)

{
  "status": "ok"
}

Response (still processing)

{
  "status": "submitted"
}
Only completed jobs can be deleted. Deletion removes both the physical file and the database record.

Upload a File

POST /files

Uploads a file to the organization's file storage. Uploaded files can be referenced in project pragmas (e.g., as image sources).

Request

Multipart form data with a file field.

# Example with curl
curl -X POST \
  -H "x-api-key: ta_your_key" \
  -F "file=@/path/to/image.png" \
  https://rest.slideform.co/files

Response

{
  "status": "ok",
  "id": "image.png",
  "file_name": "/files/uploads/org/image.png",
  "image_store": "local",
  "ts": 1710504600.0
}
FieldTypeDescription
statusstring"ok"
idstringFile identifier (original filename)
file_namestringFull storage path
image_storestringStorage type ("local")
tsnumberUnix timestamp of upload

Other

Send Email

POST /email/send

Sends an email with pre-generated HTML content. Recipients are validated against the organization's allowed domain list (if configured).

Request Body

FieldRequiredTypeDescription
html Required string HTML content of the email body
recipients Required array or string Email address(es). A single string is accepted.
subject Optional string Subject line. Defaults to "Email from Slideform".
project_id Optional string Project ID for logging

Example Request

{
  "html": "<html><body><h1>Report</h1></body></html>",
  "subject": "Monthly Report - March 2026",
  "recipients": ["user@example.com", "manager@example.com"],
  "project_id": "abc123"
}

Response (success)

{
  "status": "ok",
  "message": "Email sent to 2 recipient(s)"
}

Response (domain validation failure)

{
  "status": "failed",
  "error": "Recipients not in allowed domains: external@other.com"
}
If the organization has an email domain allowlist, all recipient addresses are validated. Wildcard domains (e.g., *.example.com) are supported.

Get Usage & Quota

GET /usage

Returns the organization's current plan, remaining slide quota, and AI add-on usage.

Parameters

None.

Response

{
  "status": "ok",
  "remaining_slides": 450,
  "plan": "Professional",
  "addon": {
    "max_monthly_insights": 100,
    "remaining_insights": 72,
    "num_monthly_insights": 28
  }
}
FieldTypeDescription
remaining_slidesnumberSlides remaining in current billing period
planstringActive plan name
addonobject or nullAI add-on usage (only if organization has AI add-on)

Health Check

GET /health

No authentication required. Returns "healthy" (plain text) if the service is running.