Project Workflow Integration

The Project Workflow Integration API provides an asynchronous flow that uploads a file from an external system, automatically runs the workflow you configured on the project, lets you poll for the processing status, and returns the result as a ZIP.

When a file is uploaded, it is processed automatically while the project's active workflow is running. The caller does not need to send a separate "start execution" request.

All API requests require API key authentication.


Prerequisites

  • An API key must be issued. See the Authentication page.
  • Identify the project ID that will receive the file. Use the publicId value returned by GET /projects/summary below.
  • The project's active workflow must be running. If it is stopped, the upload will succeed but analysis will not start, so start it from the project's Workflows menu in advance.
  • The API key user must hold administrator permission on that project. Without it, the upload returns 403.

Authentication

All API requests must include an API key in the HTTP header. The API key follows Bearer token authentication.

Header Value
Authorization Bearer {API_KEY}

Replace {API_KEY} with your actual issued API key in the request.


End-to-end Flow

This API works as a three-step asynchronous flow.

  1. Upload the file — Call POST /projects/{projectId}/documents/upload and receive a documentUuid.
  2. Poll the status — Call GET /external/documents/{uuid}/status periodically to check progress. Stop polling once it reaches a terminal status (succeeded or failed).
  3. Download the result — Once status is succeeded, call GET /external/documents/{uuid}/export to download the result ZIP.

Endpoints

`GET /projects/summary`

Returns the external ID (publicId) and name (name) of the projects owned by the authenticated API key client.

Use the publicId value from this response as the projectId path parameter of the file upload request. Add includeFolders=true to also receive each project's folder list, which you can use when specifying folderId on upload.

This API returns only the projects you own. An administrator who is not the owner will not see the project in this list even though they can upload to it — ask the project owner for the ID in that case.

Request Examples

cURL

# Projects only
curl -X GET 'https://origin.synapfoundry.ai/api/v1/projects/summary' \
  -H 'Authorization: Bearer {API_KEY}'

# Including folders
curl -X GET 'https://origin.synapfoundry.ai/api/v1/projects/summary?includeFolders=true' \
  -H 'Authorization: Bearer {API_KEY}'

Python

import requests

api_key = "{API_KEY}"
url = "https://origin.synapfoundry.ai/api/v1/projects/summary"

headers = { "Authorization": f"Bearer {api_key}" }
response = requests.get(url, headers=headers)
projects = response.json()

Node.js

const axios = require('axios');

const apiKey = "{API_KEY}";
const url = 'https://origin.synapfoundry.ai/api/v1/projects/summary';

axios.get(url, {
  headers: { 'Authorization': `Bearer ${apiKey}` }
}).then(response => {
  console.log(response.data);
}).catch(error => {
  console.error('Error:', error.response?.data || error.message);
});

Headers

Parameter Type Description
Authorization string Bearer token for API request authentication. Must be provided in the format Bearer {API_KEY}.

Query Parameters

Parameter Type Required Description
includeFolders boolean No If true, each project includes its folder list (folders). Defaults to false.

Success Responses

Status Code Media Type Description
200 OK application/json Returns the list of owned projects.

Response Example (includeFolders=true)

[
  {
    "publicId": "Ab1Cd2Ef3G",
    "name": "My Project",
    "folders": [
      { "id": 3, "name": "Contracts", "parentFolderId": null },
      { "id": 4, "name": "2026", "parentFolderId": 3 }
    ]
  },
  {
    "publicId": "Xy9Zw8Vt7U",
    "name": "Analysis Project",
    "folders": []
  }
]

Response Fields

Field Type Description
publicId string The external identifier of the project (10-character alphanumeric string). Use this value as the projectId path parameter of the file upload request.
name string The name of the project.
folders array Included only when includeFolders=true. The list of folders in the project.
folders[].id number The folder ID. Use this value as the folderId query parameter on upload.
folders[].name string The folder name.
folders[].parentFolderId number | null The parent folder's ID, or null for top-level folders.

Error Responses

Status Code Message Description
401 Unauthorized - Returned when the request is made with an invalid or expired API key.
500 Internal Server Error Internal server error Returned when request processing fails due to an unexpected internal server issue.

`POST /projects/{projectId}/documents/upload`

Uploads a file to the project. If the project's active workflow is running, the backend automatically triggers that workflow. Use the documentUuid returned in the response for the subsequent status polling and export steps.

Request Examples

cURL

curl -X POST 'https://origin.synapfoundry.ai/api/v1/projects/{PROJECT_ID}/documents/upload' \
  -H 'Authorization: Bearer {API_KEY}' \
  -F 'file=@{FILE_PATH}'

Python

import requests

api_key = "{API_KEY}"
file_path = "/path/to/your/file.ext"
url = "https://origin.synapfoundry.ai/api/v1/projects/{PROJECT_ID}/documents/upload"

headers = { "Authorization": f"Bearer {api_key}" }

with open(file_path, "rb") as file:
    files = { "file": file }
    response = requests.post(url, headers=headers, files=files)

document_uuid = response.json()["documentUuid"]

Node.js

const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');

const apiKey = "{API_KEY}";
const projectId = "{PROJECT_ID}";
const filePath = "/path/to/your/file.ext";
const url = `https://origin.synapfoundry.ai/api/v1/projects/${projectId}/documents/upload`;

const form = new FormData();
form.append('file', fs.createReadStream(filePath));

const headers = {
  ...form.getHeaders(),
  'Authorization': `Bearer ${apiKey}`,
};

axios.post(url, form, { headers }).then(response => {
  const documentUuid = response.data.documentUuid;
  console.log(documentUuid);
}).catch(error => {
  console.error('Error:', error.response?.data || error.message);
});

Headers

Parameter Type Description
Authorization string Bearer token for API request authentication. Must be provided in the format Bearer {API_KEY}.

Path Parameters

Parameter Type Required Description
projectId string Yes The external ID of the project to upload to (10-character alphanumeric string). Use the publicId value returned by GET /projects/summary.

Query Parameters

Parameter Type Required Description
folderId number No The ID (positive integer) of the folder to place the file in. Use a folders[].id value returned by GET /projects/summary?includeFolders=true. If omitted, the file is uploaded to the project root. Only folders belonging to that project can be specified.

Body

Parameter Type Required Description
file file Yes The single file to upload. Sent via the file field of multipart/form-data.

Success Responses

Status Code Media Type Description
201 Created application/json Returns the identifiers of the created document on successful upload.

Response Example

{
  "documentId": 1289,
  "documentUuid": "550e8400-e29b-41d4-a716-446655440000",
  "projectPublicId": "Ab1Cd2Ef3G"
}

Response Fields

Field Type Description
documentId number Internal identifier of the document. Not used in later steps.
documentUuid string The document UUID. Used for status polling and export.
projectPublicId string The external ID of the project the document was uploaded to. Same value as the projectId in the request path.

Error Responses

Status Code Message Description
400 Bad Request 업로드할 파일이 필요합니다. (An upload file is required.) Returned when the file field cannot be found in the multipart/form-data request.
400 Bad Request folderId 는 양의 정수여야 합니다. (folderId must be a positive integer.) Returned when folderId is not an integer or is zero or negative.
401 Unauthorized "API Key authentication failed. Please use a valid API Key for your request." Returned when the API key is missing or invalid.
403 Forbidden Project context could not be resolved Returned when projectId is not a 10-character alphanumeric string, or no project matches it. The two cases are not distinguished.
403 Forbidden Permission denied: data.access.all Returned when the project exists but the API key user has no data access permission on it.
403 Forbidden Admin permission required for this project Returned when the user has data access but is not an administrator of the project.
404 Not Found 폴더를 찾을 수 없습니다. (Folder not found.) Returned when no folder matches folderId or it does not belong to this project. The two cases are not distinguished.
413 Payload Too Large File size exceeds 100MB. Returned when the size of a single uploaded file exceeds the maximum size (100MB) allowed by the server.
415 Unsupported Media Type Unsupported file format. Returned when the format of the uploaded file is not supported by the server. Check Supported Formats.

`GET /external/documents/{uuid}/status`

Returns the workflow processing status of the uploaded document as a simple flag. Stop polling immediately once the status reaches a terminal value.

Status Values

Value Meaning terminal?
pending Waiting to be processed no
processing In progress no
review_pending Awaiting human review (HITL) no
succeeded Completed — result is ready to download yes
failed Failed / aborted / out of credits yes

Clients should treat any unknown value as terminal and stop polling. The backend safely normalizes unknown internal states to failed, but defensive handling is recommended.

  • Interval: Start at 5 seconds with exponential backoff (e.g., 5s → 10s → 20s → up to 60s).
  • Maximum duration: Varies by document size and workflow, but typically within 30 minutes. Contact operations if it exceeds.
  • Retries: Retry 5xx responses with backoff. Do not retry 4xx responses.

Request Examples

cURL

curl -X GET 'https://origin.synapfoundry.ai/api/v1/external/documents/{DOC_UUID}/status' \
  -H 'Authorization: Bearer {API_KEY}'

Python

import requests
import time

api_key = "{API_KEY}"
url = "https://origin.synapfoundry.ai/api/v1/external/documents/{DOC_UUID}/status"
headers = { "Authorization": f"Bearer {api_key}" }

interval = 5
max_seconds = 1800  # 30-minute safety cap
elapsed = 0

while elapsed < max_seconds:
    response = requests.get(url, headers=headers)
    if response.status_code >= 500:
        # 5xx: retry after backoff
        time.sleep(interval)
        elapsed += interval
        interval = min(interval * 2, 60)
        continue
    response.raise_for_status()  # 4xx: raise immediately and stop polling
    status = response.json()["status"]
    if status in ("succeeded", "failed"):
        break
    time.sleep(interval)
    elapsed += interval
    interval = min(interval * 2, 60)
else:
    raise TimeoutError("Polling exceeded 30 minutes.")

Node.js

const axios = require('axios');

const apiKey = "{API_KEY}";
const docUuid = "{DOC_UUID}";
const url = `https://origin.synapfoundry.ai/api/v1/external/documents/${docUuid}/status`;

async function pollStatus() {
  let interval = 5000;
  const maxMs = 30 * 60 * 1000; // 30-minute safety cap
  let elapsed = 0;

  while (elapsed < maxMs) {
    try {
      const { data } = await axios.get(url, {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });
      if (data.status === 'succeeded' || data.status === 'failed') {
        return data.status;
      }
    } catch (err) {
      const code = err.response?.status;
      if (code && code >= 400 && code < 500) {
        // 4xx: do not retry — stop polling
        throw err;
      }
      // 5xx / network errors: retry after backoff
    }
    await new Promise(r => setTimeout(r, interval));
    elapsed += interval;
    interval = Math.min(interval * 2, 60000);
  }
  throw new Error('Polling exceeded 30 minutes.');
}

Headers

Parameter Type Description
Authorization string Bearer token for API request authentication. Must be provided in the format Bearer {API_KEY}.

Path Parameters

Parameter Type Required Description
uuid string Yes The documentUuid returned from the upload response.

Success Responses

Status Code Media Type Description
200 OK application/json Returns the current processing status.

Response Example

{ "status": "processing" }

Error Responses

Status Code Message Description
401 Unauthorized "API Key authentication failed. Please use a valid API Key for your request." Returned when the API key is missing or invalid.
404 Not Found { "statusCode": 404, "message": "Not Found" } Returned identically when the UUID does not exist, does not belong to the caller, or is malformed (to prevent information disclosure). Stop polling.

`GET /external/documents/{uuid}/export`

Downloads the result of the workflow's final component as a ZIP for a document that completed successfully. Call this right after the polling API returns succeeded.

ZIP Contents by Final Component

The contents of the ZIP differ depending on the workflow's final output component.

Final Component ZIP Contents
Analyze The cached analysis result ZIP as-is
Review The review-completed result ZIP
Transform Analysis Result Conversion result files bundled into a single ZIP
LLM processing A ZIP containing the LLM response text as a single .md or .txt entry

Because the internal layout differs per component, integration clients are recommended to either know the final output component type in advance or process the ZIP entries generically (by iterating entries).

Request Examples

cURL

curl -L 'https://origin.synapfoundry.ai/api/v1/external/documents/{DOC_UUID}/export' \
  -H 'Authorization: Bearer {API_KEY}' \
  -o result.zip

Python

import requests

api_key = "{API_KEY}"
url = "https://origin.synapfoundry.ai/api/v1/external/documents/{DOC_UUID}/export"

response = requests.get(url, headers={ "Authorization": f"Bearer {api_key}" })

with open("result.zip", "wb") as f:
    f.write(response.content)

Node.js

const axios = require('axios');
const fs = require('fs');

const apiKey = "{API_KEY}";
const docUuid = "{DOC_UUID}";
const url = `https://origin.synapfoundry.ai/api/v1/external/documents/${docUuid}/export`;

axios.get(url, {
  headers: { 'Authorization': `Bearer ${apiKey}` },
  responseType: 'stream'
}).then(response => {
  response.data.pipe(fs.createWriteStream('result.zip'));
}).catch(error => {
  console.error('Error:', error.response?.data || error.message);
});

Headers

Parameter Type Description
Authorization string Bearer token for API request authentication. Must be provided in the format Bearer {API_KEY}.

Path Parameters

Parameter Type Required Description
uuid string Yes The documentUuid returned from the upload response.

Success Responses

Status Code Media Type Description
200 OK application/zip Returns the workflow result ZIP. The response also includes Content-Disposition: attachment; filename="<uuid>.zip".

Error Responses

Status Code Message Description
401 Unauthorized "API Key authentication failed. Please use a valid API Key for your request." Returned when the API key is missing or invalid.
404 Not Found { "statusCode": 404, "message": "Not Found" } Returned identically when the UUID does not exist, does not belong to the caller, or is malformed.
409 Conflict { "code": "NOT_EXPORTABLE", "status": "<current status>" } Document is not yet exportable. The object on the left is the entire response body (no statusCode or message wrapper). Go back to the polling API, wait for succeeded, and retry.
500 Internal Server Error Internal server error A transient internal error. Retry with exponential backoff.