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

# Create & poll jobs

> The Starfort batch de-identification job APIs: POST /v1/guard/s3 to create a job, GET to poll status, and the polling pattern (Starfort v1.4 docs)

A batch integration is three steps: **create a job → poll → collect the output**. Both APIs authenticate with the [same API key](/en/v1.4/api/authentication).

## Job creation API

```
POST https://bastion-guardian-api.starfort.io/v1/guard/s3
```

**Request headers**

| Header                     | Required | Description                    |
| -------------------------- | -------- | ------------------------------ |
| `X-Starfort-Guard-Api-Key` | Yes      | Your issued `sf_`-prefixed key |
| `Content-Type`             | Yes      | `application/json`             |

**Request body**

```json theme={"dark"}
{
  "src_s3_url": "s3://your-bucket/inbox/2026/contract-001.docx",
  "tgt_s3_url": "s3://your-bucket/deidentified/",
  "process_type": "INPUT",
  "options": { "on_pass": "copy_original" }
}
```

| Field             | Required | Description                                                                                                                                                                                                    |
| ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `src_s3_url`      | Yes      | The source file's address. `s3://<bucket>/<object key>`, pointing at **one file** (1 job = 1 object), and inside the [Storage Connection](/en/v1.4/api/batch/storage-connection)'s allowed read scope.         |
| `tgt_s3_url`      | Yes      | Where to write the result. A trailing `/` is interpreted as a **folder** (the output keeps the source file name); otherwise it's written as **a file with that name**. Must be inside the allowed write scope. |
| `process_type`    | Yes      | The processing type that picks which policy set to inspect with (e.g. `"INPUT"`). Must be within what the Guardian declares; case-insensitive.                                                                 |
| `options.on_pass` | No       | The output on a PASS verdict — `"copy_original"` (default, writes a copy of the original) / `"skip"` (writes nothing).                                                                                         |

<Note>
  The batch request body uses **snake\_case** (`process_type`) — unlike the inline [Guard API](/en/v1.4/api/request-format)'s `processType` (camelCase).
</Note>

**Response — `202 Accepted`**

```json theme={"dark"}
{ "job_id": "cmd8h2xk10003abcdxyz01234", "status": "PENDING" }
```

Keep the `job_id` for polling.

**When things are validated** — at intake, only authentication, the Kill Switch, `process_type`, and the allowed scopes are validated synchronously (on failure, no job is created — the request is rejected immediately). The source's existence, size, and format are checked at processing time; problems there finalize the job as `FAILED`. See [Batch errors](/en/v1.4/api/batch/errors).

**Retries and duplicates** — there is no idempotency token. Re-sending the same request creates a separate job each time. A lost response can lead to a duplicate submission, but the output for the same input and policy is written atomically under the same name, so `tgt` ends up in the same final state. If you've lost an accepted job's `job_id`, recover it with the source-address search below.

## Job status API

```
GET https://bastion-guardian-api.starfort.io/v1/guard/s3/jobs/{job_id}
```

**Response — `200`**

```json theme={"dark"}
{
  "job_id": "cmd8h2xk10003abcdxyz01234",
  "status": "COMPLETED",
  "action": "MASK",
  "src_s3_url": "s3://your-bucket/inbox/2026/contract-001.docx",
  "tgt_s3_url": "s3://your-bucket/deidentified/contract-001.docx",
  "results": [
    {
      "policy_name": "PII Masking Policy", "policy_type": "PII", "action": "MASK",
      "detected_items": [
        { "rule_type": "regex", "rule_name": "ssn:_us_social_security_number", "action": "MASK", "confidence": 1.0, "mask_word": "SSN_1", "alert_message": "social security number detected" }
      ]
    }
  ],
  "error": null,
  "created_at": "2026-07-23T09:00:00.000Z",
  "completed_at": "2026-07-23T09:00:41.000Z"
}
```

| Field                         | Description                                                                                                                                                                                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `status`                      | One of the four job states — `PENDING` / `PROCESSING` / `COMPLETED` / `FAILED`. See the [batch overview](/en/v1.4/api/batch/overview).                                                                                                                       |
| `action`                      | The verdict when `COMPLETED` (`PASS` / `MASK` / `BLOCK`); otherwise `null`. See [Actions](/en/v1.4/concepts/actions-pass-mask-block).                                                                                                                        |
| `tgt_s3_url`                  | **The confirmed address of the result file actually written** (including the file name when you requested a folder). `null` if no file was written.                                                                                                          |
| `results`                     | Per-policy detections (which policies fired, detected items, mask tokens, confidence) — the same result structure as the [Guard API response](/en/v1.4/api/response-format), but **never the output content itself** (the content lives only in the bucket). |
| `error`                       | The reason when `FAILED` (`code` / `message` / `details`). See [Batch errors](/en/v1.4/api/batch/errors).                                                                                                                                                    |
| `created_at` / `completed_at` | Intake time / finalization time.                                                                                                                                                                                                                             |

* A job can be queried **only with an API key from the project that created it**.
* While a [Kill Switch](/en/v1.4/admin/kill-switch) is active, both job creation and status queries are rejected.
* Job-record retention follows operational policy; for past jobs, the place to look is [Opticon](/en/v1.4/admin/monitoring-opticon) — each job's trace records the intake details (`src_s3_url` · `tgt_s3_url` · `process_type` · `job_id`) together with the verdict and any error.

### Search jobs by source address

If you've lost a `job_id`, recover it by searching recent jobs by source address:

```
GET /v1/guard/s3/jobs?src_s3_url=s3://your-bucket/inbox/2026/contract-001.docx
```

Returns the **recent jobs** submitted for that source object, newest first (up to 20 by default, `results` omitted — get the details from the single-job query with the `job_id`).

```json theme={"dark"}
{ "jobs": [ { "job_id": "cmd8h2xk10003abcdxyz01234", "status": "COMPLETED", "action": "MASK", "src_s3_url": "s3://your-bucket/inbox/2026/contract-001.docx", "tgt_s3_url": "s3://your-bucket/deidentified/contract-001.docx", "error": null, "created_at": "2026-07-23T09:00:00.000Z", "completed_at": "2026-07-23T09:00:41.000Z" } ] }
```

## The polling pattern

<Steps>
  <Step title="Create the job">Call `POST /v1/guard/s3` and keep the `job_id` from the response.</Step>
  <Step title="Poll">Call `GET /v1/guard/s3/jobs/{job_id}` periodically. While `status` is `PENDING` / `PROCESSING`, keep waiting.</Step>
  <Step title="Branch on the final state">On `COMPLETED`, branch on `action` (`PASS` / `MASK`: collect the output from `tgt_s3_url`; `BLOCK`: no output — the reason is in `results`). On `FAILED`, branch on `error.code`.</Step>
</Steps>

```bash theme={"dark"}
curl -X POST https://bastion-guardian-api.starfort.io/v1/guard/s3 \
  -H "X-Starfort-Guard-Api-Key: sf_xxxx..." -H "Content-Type: application/json" \
  -d '{ "src_s3_url": "s3://your-bucket/inbox/customer-list.xlsx", "tgt_s3_url": "s3://your-bucket/deidentified/", "process_type": "INPUT" }'
# → 202 { "job_id": "cmd8h2xk1...", "status": "PENDING" }

curl https://bastion-guardian-api.starfort.io/v1/guard/s3/jobs/cmd8h2xk1... \
  -H "X-Starfort-Guard-Api-Key: sf_xxxx..."
# → 200 { "status": "COMPLETED", "action": "MASK", "tgt_s3_url": "s3://your-bucket/deidentified/customer-list.xlsx", ... }
```
