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

# How to Detect and Start Export Jobs for Processing

export const WhatComesNext = ({children, href}) => <div className="mt-4">
    <a href={href} className="
        inline-flex items-center justify-center
        rounded-full
        bg-black text-white dark:bg-[#1f262b]
        px-5 py-2.5 text-sm font-medium
        no-underline border-0
        hover:bg-[#ffe6ea] dark:hover:bg-[#2b1f23]
        hover:text-black
        transition-colors
      ">
      {children} →
    </a>
  </div>;

export const DetectAndStartExportJobsDiagram = () => {
  const diagram = `
%%{init: {"themeVariables": {"fontSize": "28px"}}}%%
flowchart LR
    S1["1. Detect Available Jobs"] --> S2["2. Confirm Eligible Job Status"] --> S3["3. Select Oldest Job"] --> S4["4. Verify Job Status"]
    S4 -->|pending| S5["5. Start Export Job"]
    S4 -->|in_progress| S7["7. Process Timely"]
    S5 --> S7
    S5 -->|conflict 422| S6["6. Handle Conflicts"]

click S1 "#1-detect-available-export-jobs"
click S2 "#2-filter-eligible-export-jobs"
click S3 "#3-select-the-oldest-export-job"
click S4 "#4-verify-the-job-status"
click S5 "#5-start-the-export-job"
click S6 "#6-handle-concurrent-start-conflicts-safely"
click S7 "#7-process-the-job-in-a-timely-manner"

style S1 white-space:normal
style S2 white-space:normal
style S3 white-space:normal
style S4 white-space:normal
style S5 white-space:normal
style S6 white-space:normal
style S7 white-space:normal
`;
  return <Mermaid chart={diagram} />;
};

export const WorkflowDiagramTopNav = ({highlight}) => {
  const highlightStyle = highlight ? `style ${highlight} stroke:#f63b92,stroke-width:5px` : "";
  const diagram = `
%%{init: {"themeVariables": {"fontSize": "28px"}}}%%
flowchart LR

  WEBAPP["Queue Export Items in Pleo's Web App"]

subgraph Pleo["Pleo API's"]
    START1[" "]
    A["1.Detect & Start Export Jobs"] --> B["2.Perform Pre-Export Validation"]
    B --> C["3.Retrieve Export Job Items for Processing"]
    C --> D["4.Fetch Export Item Data for Processing"]
end

subgraph ERP["AS/ERP"]
    START2[" "]
    E["5.Process & Record Export Items"]
end

subgraph Pleo2["Pleo API's"]
    START3[" "]
    F["6.Update Export Items"] --> G["7.Update & Complete Export Job"]
end
WEBAPP --> A
D --> E
E --> F

click WEBAPP "/docs/current/how-tos/accounting-integrations/how-to-queue-export-items-in-ui"
click A "/docs/current/how-tos/accounting-integrations/how-to-detect-and-start-export-jobs-for-as-erp-processing"
click B "/docs/current/how-tos/accounting-integrations/how-to-perform-pre-export-validation-for-as-erp-processing"
click C "/docs/current/how-tos/accounting-integrations/how-to-retrieve-export-job-items-for-as-erp-processing"
click D "/docs/current/how-tos/accounting-integrations/how-to-fetch-export-item-data-for-as-erp-processing"
click E "/docs/current/how-tos/accounting-integrations/how-to-determine-the-bookkeeping-method-for-as-erp-processing"
click F "/docs/current/how-tos/accounting-integrations/how-to-update-export-items-for-as-erp-processing"
click G "/docs/current/how-tos/accounting-integrations/how-to-update-and-complete-export-job-for-as-erp-processing"

style WEBAPP white-space:normal
style Pleo white-space:normal
style ERP white-space:normal
style Pleo2 white-space:normal
style A white-space:normal
style B white-space:normal
style C white-space:normal
style D white-space:normal
style E white-space:normal
style F white-space:normal
style G white-space:normal

style START1 fill:transparent,stroke:transparent,color:transparent
style START2 fill:transparent,stroke:transparent,color:transparent
style START3 fill:transparent,stroke:transparent,color:transparent
style Pleo fill:none,stroke:#000000
style ERP fill:none,stroke:#000000
style Pleo2 fill:none,stroke:#000000

${highlightStyle}
`;
  return <Mermaid chart={diagram} />;
};

export const NoteCallout = ({title, children, icon = "💡"}) => <div style={{
  backgroundColor: 'var(--recommended-bg)',
  borderLeft: '4px solid #3beaf6',
  borderRadius: '10px',
  padding: '18px 22px',
  marginBottom: '20px',
  boxShadow: '1px 1px 3px rgba(0,0,0,0.06)'
}}>
    <div style={{
  display: 'flex',
  alignItems: 'flex-start',
  gap: '14px'
}}>
      <span style={{
  fontSize: '22px',
  lineHeight: '1',
  flexShrink: 0
}}>
        {icon}
      </span>
      <div>
        {title && <div style={{
  fontSize: '16px',
  fontWeight: 600,
  color: 'var(--recommended-title)',
  marginBottom: '6px'
}}>
            {title}
          </div>}
        <div style={{
  fontSize: '14px',
  lineHeight: 1.65
}}>
          {children}
        </div>
      </div>
    </div>
  </div>;

export const RememberCallout = ({title, children, icon = "🪢"}) => <div style={{
  backgroundColor: 'var(--recommended-bg)',
  borderLeft: '4px solid #f63b92',
  borderRadius: '10px',
  padding: '18px 22px',
  marginBottom: '20px',
  boxShadow: '1px 1px 3px rgba(0,0,0,0.06)'
}}>
    <div style={{
  display: 'flex',
  alignItems: 'flex-start',
  gap: '14px'
}}>
      <span style={{
  fontSize: '22px',
  lineHeight: '1',
  flexShrink: 0
}}>
        {icon}
      </span>
      <div>
        {title && <div style={{
  fontSize: '16px',
  fontWeight: 600,
  color: 'var(--recommended-title)',
  marginBottom: '6px'
}}>
            {title}
          </div>}
        <div style={{
  fontSize: '14px',
  lineHeight: 1.65
}}>
          {children}
        </div>
      </div>
    </div>
  </div>;

<WorkflowDiagramTopNav highlight="A" />

<div style={{ border: "2px solid #f63b92", borderRadius: "8px", padding: "16px", backgroundColor: "transparent" }}>
  <DetectAndStartExportJobsDiagram />
</div>

This how-to explains how an integration detects available Export Jobs in Pleo and starts a job for processing.

Detecting and starting an Export Job is the first step in the export workflow.

Export Jobs group expenses that are ready to be exported to an Accounting System.

At this stage, an Export Job exists with status `pending` and has not yet been started by any integration.

Your integration must:

* Detect available Export Jobs
* Select the appropriate job to process
* Start the job by marking it as `started`

This ensures export jobs are processed in a controlled and predictable way.

## Prerequisites

Before you begin:

* You're familiar with the [Export Lifecycle](/docs/current/platform/exports/lifecycle) and the [Integration Design for Detecting and Starting Export Jobs](/docs/current/integration-design/exports/integration-design-exports-detect-and-start-export-jobs)
* Your integration is authenticated using one of the [supported authentication methods](/docs/current/integration-design/auth/integration-design-auth-overview#authentication-policy-overview)
* Your integration can call Pleo's Export API endpoints
* Webhooks or polling are configured
* [Export Items have been queued in Pleo's Web App](/docs/current/how-tos/accounting-integrations/how-to-queue-export-items-in-ui)

## Steps

### 1. Detect Available Export Jobs

Detect new Export Jobs as they become available.

Three discovery strategies are supported:

#### Option A — Webhook Trigger (Recommended)

Subscribe to the [`export-job.created`](/reference/webhooks/webhook-events) webhook.

When the `export-job.created` event is received, proceed to step 2.

#### Option B — Polling

If webhooks are not supported, periodically request Export Jobs.

Polling should run on a controlled schedule (for example, every few minutes). See step 2 for an example request.

#### Option C — Ad Hoc Trigger (Optional)

If your integration supports user-initiated processing, expose a manual trigger that immediately runs the same polling logic as Option B.

This option is used **alongside** scheduled polling — not instead of it. It allows users to check for pending Export Jobs on demand rather than waiting for the next scheduled interval.

When the trigger fires, proceed to step 2.

***

### 2. Confirm Eligible Job Status

**API Endpoint**: GET [/v3/export-jobs](/reference/export-api/export-api-v3/get-a-list-of-export-jobs)

**Example parameters:** companyId: `12abc3d4-e567-890e-1234-abc56e78fabc`

Request `pending` Export Jobs from the API. If recovering from an interruption, also include `in_progress` to locate a job previously started but not finished.

**Example Pseudo:**

```pseudo theme={null}
// Normal operation
jobs = fetchExportJobs(statuses=["pending"])

// On startup or after reconnection: also check for an interrupted job
if isRecoveryStart:
    jobs = fetchExportJobs(statuses=["pending", "in_progress"])

if jobs is empty:
    exit workflow
```

#### Example Request — Normal Operation

<Tabs>
  <Tab title="OAuth 2.0">
    ```bash theme={null}
    curl -X GET "https://external.staging.pleo.io/v3/export-jobs?company_id=12abc3d4-e567-890e-1234-abc56e78fabc&statuses=pending" \
      -H "Authorization: Bearer <access_token>"
    ```
  </Tab>

  <Tab title="API Key">
    ```bash theme={null}
      curl --request GET \
      -u "pls_1ab2cd3e4f5g6h7a89b012c34de56f78_gabc90:" \
      -H "Accept: application/json;charset=UTF-8" \
      "https://external.staging.pleo.io/v3/export-jobs?company_id=12abc3d4-e567-890e-1234-abc56e78fabc&statuses=pending" \
      | jq
    ```
  </Tab>
</Tabs>

#### Example Request — Recovery

<Tabs>
  <Tab title="OAuth 2.0">
    ```bash theme={null}
    curl -X GET "https://external.staging.pleo.io/v3/export-jobs?company_id=12abc3d4-e567-890e-1234-abc56e78fabc&statuses=pending&statuses=in_progress" \
      -H "Authorization: Bearer <access_token>"
    ```
  </Tab>

  <Tab title="API Key">
    ```bash theme={null}
      curl --request GET \
      -u "pls_1ab2cd3e4f5g6h7a89b012c34de56f78_gabc90:" \
      -H "Accept: application/json;charset=UTF-8" \
      "https://external.staging.pleo.io/v3/export-jobs?company_id=12abc3d4-e567-890e-1234-abc56e78fabc&statuses=pending&statuses=in_progress" \
      | jq
    ```
  </Tab>
</Tabs>

#### Example Response

```json theme={null}
{
  "data": [
    {
      "id": "8eb648ab-464b-42a0-ba17-eda703657e33",
      "createdBy": "f1b5d950-1dbd-4493-8e8c-59fcfe13964f",
      "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
      "numberOfItems": 4,
      "status": "pending",
      "expiresIn": 3600,
      "createdAt": "2026-04-13T14:36:50Z",
      "startedAt": null,
      "lastUpdatedAt": "2026-04-13T14:36:50Z",
      "completedAt": null,
      "expiredAt": null,
      "failureReasonType": null,
      "failureReason": null,
      "isInteractive": true,
      "vendorBasedBookkeeping": false
    }
  ],
  "pagination": {
    "hasPreviousPage": false,
    "hasNextPage": false,
    "currentRequestPagination": {
      "sortingKeys": [],
      "sortingOrder": [],
      "parameters": {
        "company_id": [
          "12abc3d4-e567-890e-1234-abc56e78fabc"
        ],
        "statuses": [
          "pending"
        ]
      }
    },
    "startCursor": "AAAAAADJ3T7YEMLAE3UA=R23ERK2GJNBKBOQX5WTQGZL6GM",
    "endCursor": "AAAAAADJ3T7YEMLAE3UA=R23ERK2GJNBKBOQX5WTQGZL6GM",
    "total": 1
  }
}

```

***

### 3. Select the Oldest Export Job

From the filtered results, always select the **oldest** eligible job. Never process jobs in parallel. Maintain a single export worker to guarantee sequential execution.

**Example Pseudo:**

```pseudo theme={null}
jobToProcess = sortByCreatedAt(eligibleJobs).first()
```

***

### 4. Verify the Job Status

**API Endpoint**: GET [`/v3/export-jobs/{jobId}`](/reference/export-api/export-api-v3/get-an-export-job)

**Example parameters:** jobId: `8eb648ab-464b-42a0-ba17-eda703657e33`

Fetch the individual job to confirm its current status before proceeding. The status determines which path to take next.

**Example:**

```pseudo theme={null}
if job.status == "pending":
    // New job — proceed to Step 5 to start it
    continue to start job

elif job.status == "in_progress":
    // Recovery — this job was previously started by this integration
    // Skip Step 5 and proceed directly to pre-export validation
    continue to pre-export validation

else:
    // Job is completed, failed, or expired — nothing to do
    skip job
```

#### Example Request

<Tabs>
  <Tab title="OAuth 2.0">
    ```bash theme={null}
    curl -X GET "https://external.staging.pleo.io/v3/export-jobs/8eb648ab-464b-42a0-ba17-eda703657e33" \
      -H "Authorization: Bearer <access_token>"
    ```
  </Tab>

  <Tab title="API Key">
    ```json theme={null}
    curl --request GET \
    -u "pls_1ab2cd3e4f5g6h7a89b012c34de56f78_gabc90:" \
    -H "Accept: application/json;charset=UTF-8" \
    "https://external.staging.pleo.io/v3/export-jobs/8eb648ab-464b-42a0-ba17-eda703657e33" \
    | jq
    ```
  </Tab>
</Tabs>

#### Example Response

```json theme={null}
{
  "data": {
    "id": "8eb648ab-464b-42a0-ba17-eda703657e33",
    "createdBy": "f1b5d950-1dbd-4493-8e8c-59fcfe13964f",
    "companyId": "12abc3d4-e567-890e-1234-abc56e78fabc",
    "numberOfItems": 4,
    "status": "pending",
    "expiresIn": 3600,
    "createdAt": "2026-04-13T14:36:50Z",
    "startedAt": null,
    "lastUpdatedAt": "2026-04-13T14:36:50Z",
    "completedAt": null,
    "expiredAt": null,
    "failureReasonType": null,
    "failureReason": null,
    "isInteractive": true,
    "vendorBasedBookkeeping": false
  }
}
```

***

### 5. Start the Export Job

**API Endpoint**: POST [`/v3/export-job-events`](/reference/export-api/export-api-v3/create-an-export-job-event)

**Only applies if `job.status == "pending"` (from Step 4).** If the job is already `in_progress`, skip this step and proceed directly to pre-export validation.

Mark the Export Job event as `started` to signal to Pleo that your integration has taken responsibility for processing.

<RememberCallout title="Remember">
  Marking an Export Job Event as started via `/v3/export-job-events` (`"event": "started"`) updates the Export Job status to `"in_progress"`, which can be retrieved from `/v3/export-jobs/{jobId}`.
</RememberCallout>

**Example:**

```pseudo theme={null}
markExportJobStarted(job.id)
```

#### Example Request

<Tabs>
  <Tab title="OAuth 2.0">
    ```bash theme={null}
    curl -X POST "https://external.staging.pleo.io/v3/export-job-events" \
      -H "Authorization: Bearer <access_token>" \
      -H "Content-Type: application/json;charset=UTF-8" \
      -d '{
            "event": "started",
            "jobId": "8eb648ab-464b-42a0-ba17-eda703657e33"
          }'
    ```
  </Tab>

  <Tab title="API Key">
    ```json theme={null}
    curl --request POST \
      -u "pls_1ab2cd3e4f5g6h7a89b012c34de56f78_gabc90:" \
      -H "Accept: application/json;charset=UTF-8" \
      -H "Content-Type: application/json" \
      "https://external.staging.pleo.io/v3/export-job-events" \
      -d '{
          "event": "started",
          "jobId": "8eb648ab-464b-42a0-ba17-eda703657e33"
          }'
    ```
  </Tab>
</Tabs>

#### Example Response

If the request succeeds, rerunning the Step 4 curl command will return an updated job status of `in_progress`.

```json theme={null}
{
  "status": "in_progress"
}
```

***

### 6. Handle Concurrent Start Conflicts Safely

Integrations are designed to run a single export worker — but infrastructure doesn't always guarantee this. Rolling deployments, double-firing scheduled jobs, or a restarting worker can briefly produce two instances that both attempt to start the same job at the same moment.

If the API returns a `422 INVALID_EXPORT_JOB_STATUS_CHANGE` response, the job has already been started by another instance.

* Treat the response as expected behaviour
* Do not retry aggressively
* Fetch jobs again and continue normally

**Example:**

```pseudocode theme={null}
try:
    markJobStarted(job)
catch ConflictError:
    log("Job already started")
    restart discovery cycle
```

### 7. Process the Job in a Timely Manner

Export jobs expire after `expiresIn` seconds (e.g., 3600) since the last state-changing update (`lastUpdatedAt`).

To avoid expiration:

* Begin processing immediately after starting the job
* Ensure progress is made before the expiry window elapses
* Update the job status as work progresses to refresh `lastUpdatedAt` (e.g., move from `pending` to `in_progress`)

If a job expires:

* The status moves to `expired`
* The job must be resubmitted from Pleo's Web App
* A new job with a new `jobId` is added to the queue

Integrations should ensure their processing is **idempotent**, as items from an expired job may be included again in a newly submitted job.

***

## Result

After completing these steps:

* An Export Job has been safely detected
* The oldest eligible job has been selected
* Your integration has taken responsibility for processing
* The export workflow can proceed

***

## What Comes Next?

<WhatComesNext href="/docs/current/how-tos/accounting-integrations/how-to-perform-pre-export-validation-for-as-erp-processing">
  How to Perform Pre-Export Validation
</WhatComesNext>

***

<div className="text-xs uppercase" style={{ fontVariant: 'small-caps' }}>
  this how-to is part of:
</div>

<div className="mt-4 flex flex-wrap gap-2">
  <a
    href="/docs/current/guides/export-integration-workflow-guide"
    className="inline-flex items-center rounded-full border border-gray-300 dark:border-gray-600
px-3 py-1 text-xs font-medium
bg-white dark:bg-[#1f262b] text-black dark:text-white
hover:bg-gray-100 dark:hover:bg-[#2b2f33]
transition-colors"
  >
    Export Integration Workflow Guide
  </a>
</div>

***

## Related Reading

* [Export Lifecycle](/docs/current/platform/exports/lifecycle)
* [Connection and Authorisation Overview](/docs/current/integration-design/auth/integration-design-auth-overview)

***
