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

# Deleting Jobs & Uploads

> Remove job artifacts and uploaded files before the end of the automatic retention window.

Reducto lets you delete job artifacts and uploaded files on demand instead of waiting for the end of the automatic retention window.

<Note>
  Organizations on Growth and Enterprise tiers already benefit from Reducto's [Zero Data Retention (ZDR) policy](/security/policies), which typically purges all API processing artifacts within 24 hours. These endpoints are for cases where you need to delete artifacts before the 24-hour period.
</Note>

## What gets deleted

`DELETE /job/{job_id}` deletes the **API-layer artifacts** that Reducto stores in live systems when processing a job:

| Artifact type      | Examples                                   |
| ------------------ | ------------------------------------------ |
| Job output         | Parsed JSON, extracted data, split results |
| Intermediate files | Converted images, page renders             |

Uploaded files are managed separately. To remove a file you uploaded via `POST /upload`, use `DELETE /upload/{file_id}`.

When cleanup finishes, Reducto tombstones the job record. Tombstoning marks the record as deleted so Reducto no longer serves it through job retrieval endpoints; residual copies may remain briefly in backups or object-store versions until they are removed through ordinary backup rotation.

**What is not affected:**

* **Studio data.** Jobs created through Studio pipelines are managed separately. These endpoints only delete artifacts stored by the Reducto API.
* **Your source documents.** If you provided a URL to an externally hosted file, that file is untouched.
* **Completed downstream results.** If another job already consumed this job's output (e.g. chained extract after parse), the downstream job's own artifacts remain until you delete them separately.
* **Child jobs of a pipeline.** Each pipeline step (parse, split, extract, edit) runs as its own job with its own ID and its own artifacts. Deleting the parent pipeline job does not delete them.

***

## Delete a job

`DELETE /job/{job_id}` is asynchronous. It validates your request, marks the job as pending deletion, enqueues a background cleanup task, and returns `202 Accepted`. Artifact cleanup runs in the background.

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  api_key = os.environ["REDUCTO_API_KEY"]
  base_url = "https://platform.reducto.ai"

  response = requests.delete(
      f"{base_url}/job/{job_id}",
      headers={"Authorization": f"Bearer {api_key}"},
  )
  print(response.status_code)  # 202
  print(response.json())
  # {"job_id": "abc123"}
  ```

  ```javascript Node.js theme={null}
  const apiKey = process.env.REDUCTO_API_KEY;
  const baseUrl = "https://platform.reducto.ai";

  const response = await fetch(`${baseUrl}/job/${jobId}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  console.log(response.status);  // 202
  console.log(await response.json());
  // { job_id: "abc123" }
  ```

  ```bash cURL theme={null}
  curl -X DELETE "https://platform.reducto.ai/job/{job_id}" \
    -H "Authorization: Bearer $REDUCTO_API_KEY" \
    -w "\nHTTP status: %{http_code}\n"
  # HTTP status: 202
  ```
</CodeGroup>

### HTTP status codes

Job deletion is asynchronous, so the status codes reflect the lifecycle of the deletion process.

| Code                   | Meaning                              | When                                                                                                                                |
| ---------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| **202 Accepted**       | Deletion request accepted            | Returned by `DELETE /job/{job_id}`. Cleanup is running in the background.                                                           |
| **409 Conflict**       | Deletion in progress                 | Returned by `GET /job/{job_id}` while background cleanup is still running.                                                          |
| **410 Gone**           | Job deleted                          | Returned by `GET /job/{job_id}` after cleanup finishes and the job is tombstoned.                                                   |
| **404 Not Found**      | Job does not exist                   | The job ID is invalid or belongs to a different organization.                                                                       |
| **400 Bad Request**    | Job not in a terminal state          | The job is still pending, in progress, or completing. Only completed or failed jobs can be deleted.                                 |
| **422 Not applicable** | Deletion not available for your tier | On-demand deletion is only available to Growth and Enterprise organizations. The response body carries error code `NOT_APPLICABLE`. |

### Deleting persisted artifacts

If you used `persist_results: true` when creating the job, persisted result artifacts are kept by default even after deletion according to your account settings or agreement with Reducto. Pass `include_persisted=true` to also delete those long-retention artifacts:

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(
      f"{base_url}/job/{job_id}",
      headers={"Authorization": f"Bearer {api_key}"},
      params={"include_persisted": True},
  )
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    `${baseUrl}/job/${jobId}?include_persisted=true`,
    {
      method: "DELETE",
      headers: { Authorization: `Bearer ${apiKey}` },
    }
  );
  ```

  ```bash cURL theme={null}
  curl -X DELETE "https://platform.reducto.ai/job/{job_id}?include_persisted=true" \
    -H "Authorization: Bearer $REDUCTO_API_KEY"
  ```
</CodeGroup>

### Response

| Field    | Type     | Description                     |
| -------- | -------- | ------------------------------- |
| `job_id` | `string` | The ID of the job being deleted |

Store the deletion response with your own request logs if you need evidence that Reducto accepted the deletion request.

### Failure recovery

If the background cleanup task fails, the deletion marker is rolled back automatically. The job becomes retrievable again via `GET /job/{job_id}` and you can retry the `DELETE` request.

***

## Delete an uploaded file

`DELETE /upload/{file_id}` removes the file you uploaded via `POST /upload`. This operation is synchronous and returns immediately. Pass the full `reducto://` file ID returned by `POST /upload`.

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  api_key = os.environ["REDUCTO_API_KEY"]
  base_url = "https://platform.reducto.ai"

  response = requests.delete(
      f"{base_url}/upload/{file_id}",
      headers={"Authorization": f"Bearer {api_key}"},
  )
  print(response.json())
  # {"file_id": "reducto://abc-123-def"}
  ```

  ```javascript Node.js theme={null}
  const apiKey = process.env.REDUCTO_API_KEY;
  const baseUrl = "https://platform.reducto.ai";

  const response = await fetch(`${baseUrl}/upload/${fileId}`, {
    method: "DELETE",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  console.log(await response.json());
  // { file_id: "reducto://abc-123-def" }
  ```

  ```bash cURL theme={null}
  curl -X DELETE "https://platform.reducto.ai/upload/{file_id}" \
    -H "Authorization: Bearer $REDUCTO_API_KEY"
  ```
</CodeGroup>

### Response

| Field     | Type     | Description                         |
| --------- | -------- | ----------------------------------- |
| `file_id` | `string` | The normalized `reducto://` file ID |

<Note>
  Jobs that already processed this file are not affected. Delete each job separately if needed.
</Note>

***

## Relationship to data retention

These deletion endpoints complement, not replace, Reducto's automatic data retention policies. For the full scope of Reducto's data handling obligations, please refer to Reducto's Data Processing Agreement (DPA).

| Tier                   | Automatic retention                                                                        | Manual deletion                              |
| ---------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------- |
| Growth and Enterprise  | ZDR: all API processing artifacts typically purged within 24 hours                         | Job deletion via `DELETE /job`               |
| With `persist_results` | Results stored for the period specified in your account settings or as agreed with Reducto | Pass `include_persisted=true` to also delete |

For full details on data retention and security policies, see [Data policies & compliance](/security/policies).

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Do I need to delete child jobs separately?">
    Yes. Deleting a pipeline job does not cascade to the jobs its steps created.

    A pipeline run creates one parent job plus one child job per step. Each child job stores its own artifacts under its own job ID. `DELETE /job/{job_id}` only deletes the artifacts belonging to the job ID you pass, so deleting the parent leaves every child's artifacts in place.

    Collect the child job IDs from the pipeline response payload and delete each one, then delete the parent:

    <CodeGroup>
      ```python Python theme={null}
      import os

      import requests
      from reducto import Reducto

      api_key = os.environ["REDUCTO_API_KEY"]
      base_url = "https://platform.reducto.ai"
      client = Reducto(api_key=api_key)

      result = client.pipeline.run(pipeline_id=pipeline_id, input=file_id)

      child_job_ids = []
      parse = result.result.parse
      if isinstance(parse, list):
          child_job_ids.extend(item.job_id for item in parse)
      elif parse:
          child_job_ids.append(parse.job_id)
      extract = result.result.extract
      if isinstance(extract, list):
          child_job_ids.extend(split.result.job_id for split in extract)
      elif extract:
          child_job_ids.append(extract.job_id)

      for job_id in [*child_job_ids, result.job_id]:
          response = requests.delete(
              f"{base_url}/job/{job_id}",
              headers={"Authorization": f"Bearer {api_key}"},
          )
          print(job_id, response.status_code)  # 202 per job
      ```

      ```javascript Node.js theme={null}
      import Reducto from "reductoai";

      const apiKey = process.env.REDUCTO_API_KEY;
      const baseUrl = "https://platform.reducto.ai";
      const client = new Reducto({ apiKey });

      const result = await client.pipeline.run({ pipeline_id: pipelineId, input: fileId });

      const childJobIds = [];
      const parse = result.result.parse;
      if (Array.isArray(parse)) {
        childJobIds.push(...parse.map((item) => item.job_id));
      } else if (parse) {
        childJobIds.push(parse.job_id);
      }
      const extract = result.result.extract;
      if (Array.isArray(extract)) {
        childJobIds.push(...extract.map((split) => split.result.job_id));
      } else if (extract) {
        childJobIds.push(extract.job_id);
      }

      for (const jobId of [...childJobIds, result.job_id]) {
        const response = await fetch(`${baseUrl}/job/${jobId}`, {
          method: "DELETE",
          headers: { Authorization: `Bearer ${apiKey}` },
        });
        console.log(jobId, response.status);  // 202 per job
      }
      ```

      ```bash cURL theme={null}
      # Delete each child job ID from the pipeline response, then the parent job ID.
      for job_id in parse-456 extract-789 pipeline-abc123; do
        curl -X DELETE "https://platform.reducto.ai/job/$job_id" \
          -H "Authorization: Bearer $REDUCTO_API_KEY" \
          -w "\n$job_id: %{http_code}\n"
      done
      ```
    </CodeGroup>

    Check the status code for every request. A job you skip keeps its artifacts until the automatic retention window ends. Deletion is asynchronous, so a `202` means cleanup was accepted, not finished. Poll `GET /job/{job_id}` for each ID until it returns `410 Gone` if you need confirmation that cleanup completed.
  </Accordion>

  <Accordion title="Does deleting a job affect Reducto's ability to debug it?">
    Yes, deleting a job impacts Reducto's ability to provide support for a specific job because most indicators of job success (i.e. results) are deleted from our storage.
  </Accordion>

  <Accordion title="Can I delete Studio jobs?">
    No. Jobs created through Studio pipelines are managed separately. These endpoints only delete artifacts stored by the Reducto API.
  </Accordion>

  <Accordion title="Can I recover a deleted job?">
    No. Deleted jobs are not recoverable through the API. Back up results separately before deleting them in Reducto's database if you need them later.
  </Accordion>

  <Accordion title="409: Deletion in progress">
    The job's artifacts are being cleaned up in the background. Wait and retry your `GET /job/{job_id}` request. Once cleanup finishes, the status changes to `410 Gone`.
  </Accordion>

  <Accordion title="410: Job has been deleted">
    The job was deleted from live systems (either manually via this endpoint or by automatic retention). The job record is tombstoned and is no longer retrievable through the API.
  </Accordion>

  <Accordion title="400: Job not in a terminal state">
    Only completed or failed jobs can be deleted. If the job is still processing, wait for it to finish or cancel it first via `POST /cancel/{job_id}`.
  </Accordion>

  <Accordion title="What happens if deletion fails?">
    The deletion marker is rolled back automatically. The job becomes retrievable again and you can retry the `DELETE` request.
  </Accordion>
</AccordionGroup>
