> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wokelo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Workflow

> Trigger any of Wokelo's pre-built specialist workflows — IC memos, CIM reviews, deal factsheets, M&A opportunity identification, value chain analysis, market sizing, and more — via API, using the same async submit-poll-download pattern as all Workflow APIs.

## 1. Overview

The Custom Workflow API gives you programmatic access to Wokelo's full library of specialist research workflows — templates that go well beyond the standard Company Research, Industry Research, and Peer Comparison formats. Each workflow is purpose-built for a specific investment or diligence use case, producing a structured output tailored to that task rather than a generic company or sector brief.

This is an **asynchronous POST API** — submitting a request returns a `report_id` immediately. You then poll for completion using the [Report Status](/supporting-apis-doc#report-status) endpoint and retrieve the finished report using the [Download Report](/supporting-apis-doc#download-report) endpoint.

**The three-step workflow:**

```text theme={"system"}
Step 1: POST /api/workflow_manager/start/         → returns report_id
Step 2: GET  /api/assets/get_notebook_status/     → poll until status = "Completed"
Step 3: POST /api/assets/download_report/         → retrieve JSON / PDF / DOCX / PPT
```

**What makes Custom Workflow different from other Workflow APIs:**

* The `workflow` field is a **variable workflow ID** — not a fixed string like `"company_primer"` or `"industry_primer"`. Each pre-built template has its own ID (e.g. `"value_creation_e469641"`, `"ic_memo_(direct_investments)_02918b9"`). You select the template by passing its ID.
* The **required input fields depend on the workflow type**: company-focused workflows require `permalink` or `website`; industry-focused workflows require `industry_research_topic`. Some workflows accept both.
* The **output structure varies by workflow** — each template produces its own named sections and step structure, rather than a fixed schema.
* The endpoint, auth mechanism, polling flow, and download mechanics are **identical to all other Workflow APIs**. Only the workflow ID and inputs change.

**Available pre-built workflows:**

| Workflow ID                             | Name                             | Type     | Description                                                                                         |
| --------------------------------------- | -------------------------------- | -------- | --------------------------------------------------------------------------------------------------- |
| `customer_call_synthesis_462a7bb`       | Customer Call Synthesis          | Company  | Summarise customer feedback insights and key discussion points from call transcripts                |
| `fact_sheet_v1_274e768`                 | Deal Factsheet                   | Company  | Generate a concise deal snapshot for IC review — one-pager format                                   |
| `expert_call_synthesis_f6a6a60`         | Expert Call Synthesis            | Company  | Summarise key insights and takeaways from expert interviews                                         |
| `ic_memo_(direct_investments)_02918b9`  | IC Memo (Direct Investments)     | Company  | Create a full investment memo for evaluating direct investment opportunities                        |
| `ic_memo_(fund)_e41b13a`                | IC Memo (Fund)                   | Company  | Structured due diligence memo on a target fund                                                      |
| `value_chain_v1_bac0ea9`                | Value Chain Analysis             | Company  | Analyse activities across the value chain to identify value creation and efficiency opportunities   |
| `week_0_cdd_fact_pack_c0fd566`          | Week-0 CDD Factpack              | Company  | Build a factpack for preliminary commercial due diligence                                           |
| `martec_cim_review_memo_71a4d4a`        | CIM Review                       | Company  | Analyse a Confidential Information Memorandum for strategic deal insights                           |
| `value_creation_e469641`                | M\&A Opportunity Identification  | Company  | Identify potential acquisition themes for a company based on strategic desirability and feasibility |
| `industry_research___custom_a842447`    | Industry Attractiveness          | Industry | Assess industry attractiveness, market potential, and competitive dynamics                          |
| `industry_level_unit_economics_ac92754` | Industry Unit Economics *(beta)* | Industry | Analyse industry-level unit economics and margin structures                                         |
| `new_market_sizing_work`                | Market Sizing *(beta)*           | Industry | Estimate total addressable market opportunity for a defined space                                   |
| `company_teaser_ad4f4ca`                | Company Teaser                   | Company  | Generate a concise, deal-ready company teaser for deal marketing purposes                           |

<Info>
  This API uses the same endpoint, auth mechanism, polling flow, and download mechanics as Company Research, Industry Research, and Peer Comparison. The only differences are the workflow ID you pass and the input fields required by that workflow type.
</Info>

***

## 2. Quick Start

**Step 1 — Submit a company workflow request**

<CodeGroup>
  ```bash cURL theme={"system"}
  curl --location 'https://api.wokelo.ai/api/workflow_manager/start/' \
    --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
      "workflow": "value_creation_e469641",
      "permalink": "air-canada"
    }'
  ```

  ```python Python theme={"system"}
  import requests

  response = requests.post(
      "https://api.wokelo.ai/api/workflow_manager/start/",
      headers={
          "Authorization": "Bearer <YOUR_API_TOKEN>",
          "Content-Type": "application/json"
      },
      json={
          "workflow": "value_creation_e469641",
          "permalink": "air-canada"
      }
  )
  report_id = response.json()["report_id"]
  print(f"Report submitted. report_id: {report_id}")
  ```
</CodeGroup>

**Step 1 (alternative) — Submit an industry workflow request**

```python theme={"system"}
response = requests.post(
    "https://api.wokelo.ai/api/workflow_manager/start/",
    headers={
        "Authorization": "Bearer <YOUR_API_TOKEN>",
        "Content-Type": "application/json"
    },
    json={
        "workflow": "industry_research___custom_a842447",
        "industry_research_topic": "Electric vehicles"
    }
)
report_id = response.json()["report_id"]
```

**Step 2 — Poll for completion**

```python theme={"system"}
import time, requests

def wait_for_report(report_id, api_key, poll_interval=20, timeout=900):
    headers = {"Authorization": f"Bearer {api_key}"}
    elapsed = 0
    while elapsed < timeout:
        r = requests.get(
            "https://api.wokelo.ai/api/assets/get_notebook_status/",
            headers=headers,
            params={"report_id": report_id}
        )
        status = r.json().get("status", "")
        print(f"[{elapsed}s] Status: {status}")
        if status == "Completed":
            return True
        if status == "Failed":
            raise Exception(f"Report {report_id} failed")
        time.sleep(poll_interval)
        elapsed += poll_interval
    raise TimeoutError(f"Report {report_id} did not complete within {timeout}s")

wait_for_report(report_id, api_key="<YOUR_API_TOKEN>")
```

**Step 3 — Download the report**

```python theme={"system"}
result = requests.post(
    "https://api.wokelo.ai/api/assets/download_report/",
    headers={"Authorization": "Bearer <YOUR_API_TOKEN>"},
    json={"report_id": report_id, "file_type": "json"}
)
report = result.json()
print(f"Top-level sections: {list(report.keys())}")
```

***

## 3. Authentication

All requests must include a **Bearer token** in the `Authorization` HTTP header.

```text theme={"system"}
Authorization: Bearer <YOUR_API_TOKEN>
```

API tokens are issued from your Wokelo account. Navigate to **Account Details → API Credentials** in the Wokelo dashboard to get your client id and client secret. Contact [support@wokelo.ai](mailto:support@wokelo.ai) if you do not yet have API access.

<Warning>
  Never expose your token in client-side code, browser requests, or public repositories. A missing or invalid token returns `401 Unauthorized`. A valid token without sufficient plan permissions returns `403 Forbidden`.
</Warning>

***

## 4. Request Reference

**Endpoint**

```text theme={"system"}
POST https://api.wokelo.ai/api/workflow_manager/start/
```

All parameters are passed as JSON in the request body.

| Parameter                 | Type      | Required                         | Description                                                                                                                                                                                                                                                                                                |
| ------------------------- | --------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `workflow`                | string    | **Required**                     | The workflow ID for the template you want to run. Copy the ID exactly from the table in Section 1 (e.g. `"value_creation_e469641"`). Unlike the other Workflow APIs, this is not a fixed value — it selects which specialist template to execute.                                                          |
| `permalink`               | string    | Required for company workflows\* | Wokelo/Crunchbase permalink of the company (e.g. `"air-canada"`, `"salesforce"`). Use the [Company Search API](/supporting-apis-doc#company-search) to resolve a company name to its permalink. Required for all company-type workflows.                                                                   |
| `website`                 | string    | Required for company workflows\* | Full URL of the company's website (e.g. `"https://www.aircanada.com"`). Accepted as an alternative to `permalink` when the permalink is unknown.                                                                                                                                                           |
| `industry_research_topic` | string    | Context-dependent                | **For company workflows**: optional industry override — leave blank to use Wokelo's auto-detected industry. **For industry workflows**: required — the industry, sector, or topic to analyse (e.g. `"Electric vehicles"`, `"B2B SaaS payments"`).                                                          |
| `workbook_name`           | string    | Optional                         | Label for the generated report workbook. Defaults to a generic name if omitted. Useful for identifying reports in subsequent status and download calls.                                                                                                                                                    |
| `custom_files`            | object\[] | Optional                         | Array of file references to include in the report alongside Wokelo's synthesis. Each object should use the `fileName` value returned by the [File Upload API](/supporting-apis-doc#file-upload). Use this to incorporate CIMs, management presentations, expert call transcripts, or proprietary research. |

<Info>
  For company-type workflows (all except `industry_research___custom_a842447`, `industry_level_unit_economics_ac92754`, and `new_market_sizing_work`), either `permalink` or `website` is required. For industry-type workflows, `industry_research_topic` is required and no company identifier is needed. The `industry_research_topic` field serves double duty — as an optional industry label in company workflows, and as the primary research topic in industry workflows.
</Info>

**Full request examples:**

<CodeGroup>
  ```bash cURL — company workflow theme={"system"}
  curl --location 'https://api.wokelo.ai/api/workflow_manager/start/' \
    --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
      "workflow": "ic_memo_(direct_investments)_02918b9",
      "permalink": "stripe",
      "workbook_name": "Stripe IC Memo — June 2026"
    }'
  ```

  ```bash cURL — industry workflow theme={"system"}
  curl --location 'https://api.wokelo.ai/api/workflow_manager/start/' \
    --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
      "workflow": "industry_research___custom_a842447",
      "industry_research_topic": "Electric vehicles",
      "workbook_name": "EV Industry Attractiveness"
    }'
  ```

  ```python Python theme={"system"}
  import requests

  HEADERS = {
      "Authorization": "Bearer <YOUR_API_TOKEN>",
      "Content-Type": "application/json"
  }

  # Company workflow — M&A Opportunity Identification
  response = requests.post(
      "https://api.wokelo.ai/api/workflow_manager/start/",
      headers=HEADERS,
      json={
          "workflow":      "value_creation_e469641",
          "permalink":     "air-canada",
          "workbook_name": "Air Canada M&A Opportunities"
      }
  )
  print(response.json())   # {"report_id": 86220}

  # Industry workflow — Industry Attractiveness
  response = requests.post(
      "https://api.wokelo.ai/api/workflow_manager/start/",
      headers=HEADERS,
      json={
          "workflow":                "industry_research___custom_a842447",
          "industry_research_topic": "Electric vehicles",
          "workbook_name":           "EV Industry Attractiveness"
      }
  )
  print(response.json())   # {"report_id": 86221}
  ```
</CodeGroup>

***

## 5. Response

### Submission response

The initial POST returns immediately with a single field:

```json theme={"system"}
{
  "report_id": 86220
}
```

| Field       | Type    | Description                                                                                                                      |
| ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `report_id` | integer | Unique identifier for this report job. Store immediately — it is the only handle to your report and cannot be recovered if lost. |

### Report status response

Poll `GET /api/assets/get_notebook_status/?report_id={report_id}` until the status field is `"Completed"`.

| Status value   | Meaning                                                       |
| -------------- | ------------------------------------------------------------- |
| `"Pending"`    | Report is queued and waiting to start.                        |
| `"Processing"` | Report generation is in progress.                             |
| `"Completed"`  | Report is ready to download.                                  |
| `"Failed"`     | Report generation encountered an error. Retry the submission. |

### Downloaded report structure

Unlike Company Research, Industry Research, and Peer Comparison — which have fixed, predictable section names — **Custom Workflow output structures vary by workflow ID**. Each template produces its own named top-level sections and nested sub-keys.

**General structural patterns:**

* **Multi-step workflows** (like M\&A Opportunity Identification, IC Memos) produce step-keyed top-level sections, e.g. `"Step 1: Company Information"`, `"Step 2: Company Intent Identification"`, `"Step 3: Opportunity Mapping"`, with a summary scorecard section (e.g. `"Investment Prioritization Scorecard"`)
* **Single-output workflows** (like Company Teaser, Deal Factsheet, CIM Review) produce a single section or a small number of named output blocks
* Within each step, sub-keys use a **UUID-suffixed naming convention** (e.g. `"CP - Quick snapshot_3b3f08c6-d285-4bb6-9b50-93c7e87cea9d"`) — meaning sub-key names are not deterministic and must be discovered by iterating the keys of each section

**Recommended navigation pattern:**

```python theme={"system"}
report = result.json()

# Iterate top-level sections (predictable — named by workflow template)
for section_name, section_data in report.items():
    print(f"\n=== SECTION: {section_name} ===")

    # Iterate sub-keys within each section (UUID-suffixed — discover by iteration)
    for sub_key, sub_data in section_data.items():
        print(f"  Sub-key: {sub_key}")

        # Most sub-keys have a "summary" field for the main content
        summary = None
        if isinstance(sub_data, dict):
            summary = sub_data.get("summary") or sub_data.get("quick_snapshot")
        if summary:
            content = summary if isinstance(summary, str) else str(summary)[:200]
            print(f"  Content preview: {content[:200]}...")
```

**Common sub-key fields across workflow types:**

| Field            | Type      | Present in             | Description                                                                                                                    |
| ---------------- | --------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `summary`        | string    | Most sub-keys          | Main narrative content for this step or section. Often markdown-formatted with bold text, tables, and source brackets `[1-5]`. |
| `source`         | object\[] | Most sub-keys          | Array of citation objects: `{id, title, url, publisher, date}`.                                                                |
| `quick_snapshot` | object    | Company snapshot steps | Firmographic card with `company_firmographics` and `product_details` objects (same schema as Company Research Quick Snapshot). |

**M\&A Opportunity Identification (`value_creation_e469641`) — example structure:**

```text theme={"system"}
report
├── "Investment Prioritization Scorecard"
│   └── "Opportunity Prioritization Scorecard"
│       ├── source: [...]
│       └── summary: markdown table of themes × business value × attractiveness
├── "Step 1: Company Information"
│   ├── "CP - Quick snapshot_{uuid}"
│   │   ├── quick_snapshot: {...}     ← firmographics + product details
│   │   └── sources: [...]
│   └── "CP - Executive summary_{uuid}"
│       ├── source: [...]
│       └── summary: markdown narrative
├── "Step 2: Company Intent Identification"
│   └── "Company Intent Identification"
│       ├── source: [...]
│       └── summary: markdown table of intent signals + hypothesis statements
└── "Step 3: Opportunity Mapping"
    └── "Theme Identification"
        ├── source: [...]
        └── summary: markdown narrative
```

### Download format options

| `file_type` | Description                                                                                                                                                               |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"json"`    | Fully structured JSON — all sections, step sub-keys, summary content, and source citations. Best for programmatic processing, pipeline ingestion, or downstream analysis. |
| `"pdf"`     | Formatted PDF. Best for sharing with stakeholders as a finished deliverable.                                                                                              |
| `"docx"`    | Editable Word document. Best for teams that customise or annotate outputs before distribution.                                                                            |
| `"ppt"`     | PowerPoint presentation. Best for IC presentations, LP updates, and deal memos.                                                                                           |

***

## 6. Examples

### M\&A Opportunity Identification for a company

Identify potential acquisition themes and opportunity scoring for a company.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl --location 'https://api.wokelo.ai/api/workflow_manager/start/' \
    --header 'Authorization: Bearer <YOUR_API_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
      "workflow": "value_creation_e469641",
      "permalink": "air-canada",
      "workbook_name": "Air Canada M&A Opportunity Identification"
    }'
  ```

  ```python Python theme={"system"}
  import requests, time

  API_KEY = "<YOUR_API_TOKEN>"
  HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

  # Submit
  submit = requests.post(
      "https://api.wokelo.ai/api/workflow_manager/start/",
      headers=HEADERS,
      json={
          "workflow":      "value_creation_e469641",
          "permalink":     "air-canada",
          "workbook_name": "Air Canada M&A Opportunity Identification"
      }
  )
  report_id = submit.json()["report_id"]
  print(f"Submitted. report_id: {report_id}")

  # Poll
  while True:
      r = requests.get(
          "https://api.wokelo.ai/api/assets/get_notebook_status/",
          headers=HEADERS, params={"report_id": report_id}
      )
      status = r.json().get("status")
      print(f"Status: {status}")
      if status == "Completed":
          break
      if status == "Failed":
          raise Exception("Report failed")
      time.sleep(20)

  # Download
  result = requests.post(
      "https://api.wokelo.ai/api/assets/download_report/",
      headers=HEADERS,
      json={"report_id": report_id, "file_type": "json"}
  )
  report = result.json()
  print(f"Sections: {list(report.keys())}")
  ```
</CodeGroup>

**Sample submission response:**

```json theme={"system"}
{
  "report_id": 86220
}
```

### Industry Attractiveness assessment

Run an industry-type workflow to assess attractiveness and potential for a sector.

```python theme={"system"}
import requests, time

API_KEY = "<YOUR_API_TOKEN>"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

submit = requests.post(
    "https://api.wokelo.ai/api/workflow_manager/start/",
    headers=HEADERS,
    json={
        "workflow":                "industry_research___custom_a842447",
        "industry_research_topic": "B2B Embedded Finance",
        "workbook_name":           "B2B Embedded Finance — Industry Attractiveness"
    }
)
report_id = submit.json()["report_id"]
print(f"Submitted. report_id: {report_id}")
```

### CIM Review with an uploaded document

Upload a CIM file and run the CIM Review workflow to get AI-synthesised deal insights.

```python theme={"system"}
import requests

API_KEY = "<YOUR_API_TOKEN>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Step 1: Upload the CIM
with open("target_company_cim.pdf", "rb") as f:
    upload_r = requests.post(
        "https://api.wokelo.ai/api/assets/upload/",
        headers=HEADERS,
        files={"files": ("target_company_cim.pdf", f, "application/pdf")}
    )
file_name = upload_r.json()["fileName"]

# Step 2: Submit with the uploaded file
submit_r = requests.post(
    "https://api.wokelo.ai/api/workflow_manager/start/",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "workflow":      "martec_cim_review_memo_71a4d4a",
        "permalink":     "target-company-permalink",
        "custom_files":  [{"fileName": file_name}],
        "workbook_name": "Target Co — CIM Review"
    }
)
report_id = submit_r.json()["report_id"]
print(f"CIM Review submitted. report_id: {report_id}")
```

### Navigating the output JSON

Because sub-keys within each section use UUID-suffixed names, navigate by iterating rather than hardcoding paths.

```python theme={"system"}
report = result.json()

# --- Extract the scorecard summary table ---
scorecard_section = report.get("Investment Prioritization Scorecard", {})
for sub_key, sub_data in scorecard_section.items():
    summary = sub_data.get("summary", "")
    if summary:
        print("=== SCORECARD ===")
        print(summary[:1000])

# --- Extract company snapshot from Step 1 ---
step1 = report.get("Step 1: Company Information", {})
for sub_key, sub_data in step1.items():
    if "quick_snapshot" in sub_data:
        qs   = sub_data["quick_snapshot"]
        name = qs["company_firmographics"]["organization"]["name"]
        fin  = qs["company_firmographics"]["details"].get("financials", {})
        rev  = fin.get("revenue", {})
        print(f"\n=== {name} ===")
        if rev:
            print(f"Revenue: ${rev.get('value', 0) / 1e9:.1f}B ({rev.get('period', 'N/A')})")
    elif "summary" in sub_data:
        print(f"\n--- {sub_key.split('_')[0]} ---")
        print(sub_data["summary"][:500])

# --- Extract intent signals from Step 2 ---
step2 = report.get("Step 2: Company Intent Identification", {})
for sub_key, sub_data in step2.items():
    summary = sub_data.get("summary", "")
    if summary:
        print("\n=== INTENT SIGNALS (excerpt) ===")
        print(summary[:800])

# --- Print all step summaries ---
for section_name, section_data in report.items():
    print(f"\n{'='*60}")
    print(f"SECTION: {section_name}")
    for sub_key, sub_data in section_data.items():
        if isinstance(sub_data, dict) and "summary" in sub_data:
            s = sub_data["summary"]
            if isinstance(s, str):
                print(f"  [{sub_key[:40]}] {s[:200]}...")
```

### Batch custom workflows across a portfolio

Run M\&A Opportunity Identification or IC Memo workflows across a set of companies concurrently.

```python theme={"system"}
import requests, time, concurrent.futures

API_KEY  = "<YOUR_API_TOKEN>"
HEADERS  = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

COMPANIES = [
    ("air-canada",  "value_creation_e469641"),
    ("delta-airlines", "value_creation_e469641"),
    ("southwest-airlines", "ic_memo_(direct_investments)_02918b9"),
]

def submit_workflow(permalink, workflow_id):
    r = requests.post(
        "https://api.wokelo.ai/api/workflow_manager/start/",
        headers=HEADERS,
        json={"workflow": workflow_id, "permalink": permalink}
    )
    return permalink, r.json()["report_id"]

def poll_until_done(report_id, interval=20, timeout=900):
    elapsed = 0
    while elapsed < timeout:
        r = requests.get(
            "https://api.wokelo.ai/api/assets/get_notebook_status/",
            headers=HEADERS, params={"report_id": report_id}
        )
        status = r.json().get("status")
        if status == "Completed":
            return True
        if status == "Failed":
            return False
        time.sleep(interval)
        elapsed += interval
    return False

def fetch_report(report_id):
    r = requests.post(
        "https://api.wokelo.ai/api/assets/download_report/",
        headers=HEADERS,
        json={"report_id": report_id, "file_type": "json"}
    )
    return r.json()

# Submit all concurrently
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as pool:
    futures = {pool.submit(submit_workflow, p, w): p for p, w in COMPANIES}
    jobs = {}
    for f in concurrent.futures.as_completed(futures):
        permalink, report_id = f.result()
        jobs[permalink] = report_id
        print(f"Submitted: {permalink} → {report_id}")

# Poll and collect
reports = {}
for permalink, report_id in jobs.items():
    ok = poll_until_done(report_id)
    if ok:
        reports[permalink] = fetch_report(report_id)
        print(f"Collected: {permalink}")
    else:
        print(f"Failed: {permalink}")
```

### Expert Call Synthesis with a transcript file

Upload a call transcript and run Expert Call Synthesis to extract structured takeaways.

```python theme={"system"}
import requests

API_KEY = "<YOUR_API_TOKEN>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Upload the transcript
with open("expert_call_transcript.pdf", "rb") as f:
    upload_r = requests.post(
        "https://api.wokelo.ai/api/assets/upload/",
        headers=HEADERS,
        files={"files": ("expert_call_transcript.pdf", f, "application/pdf")}
    )
file_name = upload_r.json()["fileName"]

# Submit
submit_r = requests.post(
    "https://api.wokelo.ai/api/workflow_manager/start/",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={
        "workflow":      "expert_call_synthesis_f6a6a60",
        "permalink":     "target-company-permalink",
        "custom_files":  [{"fileName": file_name}],
        "workbook_name": "Expert Call — Supply Chain VP"
    }
)
report_id = submit_r.json()["report_id"]
print(f"Synthesis submitted. report_id: {report_id}")
```

***

## 7. Error Handling

The API uses standard HTTP status codes. The submission endpoint returns errors synchronously; processing errors appear as `"Failed"` status when polling.

| Status                      | Meaning             | Cause & Resolution                                                                                                                                                                                  |
| --------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`                    | Request accepted    | `report_id` returned. Proceed to polling.                                                                                                                                                           |
| `400 Bad Request`           | Invalid parameters  | Missing `workflow`, unrecognised workflow ID, missing company identifier for a company-type workflow, or missing `industry_research_topic` for an industry-type workflow. Check the `detail` field. |
| `401 Unauthorized`          | Auth failed         | The `Authorization` header is missing or contains an invalid token. Verify your key in **Settings → API Keys**.                                                                                     |
| `403 Forbidden`             | Insufficient access | Your plan does not include access to this workflow template. Contact [support@wokelo.ai](mailto:support@wokelo.ai).                                                                                 |
| `429 Too Many Requests`     | Rate limit exceeded | Implement exponential back-off on submission. The response includes a `Retry-After` header.                                                                                                         |
| `500 Internal Server Error` | Server error        | Retry the submission after a brief delay. If the issue persists, contact [support@wokelo.ai](mailto:support@wokelo.ai).                                                                             |

**Handling a `"Failed"` report status:**

```python theme={"system"}
status_r = requests.get(
    "https://api.wokelo.ai/api/assets/get_notebook_status/",
    headers=HEADERS,
    params={"report_id": report_id}
)
if status_r.json().get("status") == "Failed":
    print(f"Report {report_id} failed. Resubmitting...")
    resubmit = requests.post(
        "https://api.wokelo.ai/api/workflow_manager/start/",
        headers=HEADERS,
        json={"workflow": "value_creation_e469641", "permalink": "air-canada"}
    )
    print(f"New report_id: {resubmit.json()['report_id']}")
```

**Retry with exponential back-off:**

```python theme={"system"}
import time, requests

def submit_with_retry(body, api_key, max_retries=3):
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }
    for attempt in range(max_retries):
        try:
            r = requests.post(
                "https://api.wokelo.ai/api/workflow_manager/start/",
                headers=headers, json=body, timeout=30
            )
            if r.status_code == 429:
                time.sleep(2 ** attempt)
                continue
            r.raise_for_status()
            return r.json()["report_id"]
        except requests.exceptions.Timeout:
            if attempt == max_retries - 1:
                raise
            time.sleep(1)
    raise Exception(f"Submission failed after {max_retries} attempts")
```

***

## 8. Best Practices

**Copy workflow IDs exactly — capitalisation, underscores, and alphanumeric suffixes all matter**

Each workflow ID is a unique string including a short hash suffix (e.g. `"value_creation_e469641"`, `"ic_memo_(direct_investments)_02918b9"`). Unlike `"company_primer"` or `"industry_primer"` which are clean, memorable strings, custom workflow IDs are exact identifiers. A single character difference returns a `400` error. Copy from the table in Section 1 — never type from memory.

**Know whether your workflow is company-type or industry-type before submitting**

Company-type workflows (all templates except the three industry ones) require `permalink` or `website`. Industry-type workflows (`industry_research___custom_a842447`, `industry_level_unit_economics_ac92754`, `new_market_sizing_work`) require `industry_research_topic`. Sending a company identifier to an industry workflow, or an industry topic without a company identifier to a company workflow, will return a `400` or produce an empty/incorrect report:

```python theme={"system"}
# ❌ Company workflow with no company identifier — will fail
{"workflow": "value_creation_e469641"}

# ✅ Company workflow with permalink
{"workflow": "value_creation_e469641", "permalink": "air-canada"}

# ❌ Industry workflow with a permalink instead of topic — will fail
{"workflow": "industry_research___custom_a842447", "permalink": "tesla-motors"}

# ✅ Industry workflow with a topic
{"workflow": "industry_research___custom_a842447", "industry_research_topic": "Electric vehicles"}
```

**Use `custom_files` to get full value from transcript-based workflows**

The Customer Call Synthesis and Expert Call Synthesis workflows produce their most useful output when you upload the actual transcript alongside the request. Without `custom_files`, these workflows will attempt to synthesise from publicly available sources, which produces generic output. Always upload the source document first:

```python theme={"system"}
# Upload → get fileName → pass in custom_files
upload_r = requests.post("https://api.wokelo.ai/api/assets/upload/", ...)
file_name = upload_r.json()["fileName"]
json_body["custom_files"] = [{"fileName": file_name}]
```

**Navigate output JSON by iterating keys — do not hardcode paths**

Unlike Company Research (which has predictable fixed section names) and Industry Research (which always produces the same five sections), Custom Workflow output structures are workflow-specific and include UUID-suffixed sub-keys. Always iterate rather than hardcoding:

```python theme={"system"}
# ❌ Brittle — assumes a specific UUID in the sub-key name
summary = report["Step 1: Company Information"]["CP - Quick snapshot_3b3f08c6-..."]["summary"]

# ✅ Resilient — discovers sub-keys by iteration
step1 = report.get("Step 1: Company Information", {})
for sub_key, sub_data in step1.items():
    if isinstance(sub_data, dict) and "summary" in sub_data:
        print(sub_data["summary"])
```

**Store `report_id` immediately and durably**

The submission response contains only `report_id`. There is no list-reports endpoint to recover lost IDs. Store it to a database or log immediately after receiving the response.

**Use 20-second polling intervals — complex multi-step workflows can take 5–10 minutes**

Workflows with multiple research steps (IC Memo, M\&A Opportunity Identification, Value Chain Analysis) run several parallel research jobs and typically complete in 5–10 minutes. Poll every 20 seconds with a progressive back-off:

```python theme={"system"}
for wait in [20, 20, 30, 30, 60, 60, 120]:
    time.sleep(wait)
    status = get_status(report_id)
    if status in ("Completed", "Failed"):
        break
```

**Check workflow availability with your plan before building automations**

Some workflow templates may be gated to specific plan tiers. A `403 Forbidden` on submission (not on polling) indicates your plan does not include access to that template. Contact [support@wokelo.ai](mailto:support@wokelo.ai) to confirm which templates are included in your plan before building production automations around them.

***

## 9. Related APIs

<CardGroup cols={3}>
  <Card title="Company Research" icon="building" href="/company-research-doc">
    The standard async company deep-dive — same endpoint and polling flow, fixed `"company_primer"` workflow producing a consistent five-section report.
  </Card>

  <Card title="Industry Research" icon="chart-line" href="/industry-research-doc">
    The standard async industry primer — same endpoint and polling flow, fixed `"industry_primer"` workflow.
  </Card>

  <Card title="Peer Comparison" icon="scale-balanced" href="/peer-comparison-doc">
    Side-by-side benchmarking of 2–5 companies — same endpoint, fixed `"player_comparison"` workflow.
  </Card>

  <Card title="Company News Monitoring" icon="newspaper" href="/company-news-monitoring-doc">
    Real-time news feed for any company — synchronous, no polling. Useful for keeping custom workflow outputs current with latest developments.
  </Card>

  <Card title="Supporting APIs" icon="wrench" href="/supporting-apis-doc">
    File Upload, Report Status, and Download Report — all required to run Custom Workflow end-to-end.
  </Card>

  <Card title="Company Search" icon="magnifying-glass" href="/supporting-apis-doc#company-search">
    Resolve a company name to its Wokelo permalink — needed before passing `permalink` to any company-type workflow.
  </Card>
</CardGroup>
