# GET /v1/bloggers/jobs/{job_id}

Poll the status of an asynchronous track-creator job enqueued via [`POST /v1/bloggers/track`](./track). The endpoint exposes a state snapshot for a single job: `job_id`, current `status`, and either `result` (on success) or `error` (on failure).

## 📋 Overview

| Property | Value |
|----------|-------|
| **Method** | `GET` |
| **Endpoint** | `https://api.influtics.com/v1/bloggers/jobs/{job_id}` |
| **Auth Required** | ✅ Yes |
| **Credits Cost** | 0 credits |

## 🔐 Headers

```
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
```

## 📝 Request

### Path Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `job_id` | `string` (UUID) | ✅ **Required** | Job UUID returned by `POST /v1/bloggers/track`. |

### Example Request

```bash
curl -X GET "https://api.influtics.com/v1/bloggers/jobs/8f3a7e21-6c4f-4d3b-9c2a-1e0b8d7c6f5a" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json"
```

## ✅ Response

The same `200 OK` envelope is returned for every terminal/non-terminal state. The shape of `data` changes with `status`.

### Status: `queued`

Initial state — the job is persisted but the consumer hasn't picked it up yet. Mirrors the `processing` shape with `started_at: null` and no `elapsed_seconds`.

```json
{
  "success": true,
  "data": {
    "job_id": "8f3a7e21-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
    "status": "queued",
    "platform": "instagram",
    "username": "bar.films",
    "created_at": "2026-08-18T12:34:56Z",
    "started_at": null,
    "elapsed_seconds": null
  },
  "meta": {
    "request_id": "be6132b9-ab8b-4fe6-895c-1277f59a0616"
  }
}
```

### Status: `processing`

The consumer has begun handling the job.

```json
{
  "success": true,
  "data": {
    "job_id": "8f3a7e21-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
    "status": "processing",
    "platform": "instagram",
    "username": "bar.films",
    "created_at": "2026-08-18T12:34:56Z",
    "started_at": "2026-08-18T12:34:57Z",
    "elapsed_seconds": 5
  },
  "meta": {
    "request_id": "be6132b9-ab8b-4fe6-895c-1277f59a0616"
  }
}
```

### Status: `succeeded`

The job finished successfully. `data.result` contains the tracked creator and blogger identifiers — you can use these as your reference for downstream calls.

```json
{
  "success": true,
  "data": {
    "job_id": "8f3a7e21-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
    "status": "succeeded",
    "platform": "instagram",
    "username": "bar.films",
    "tracked_account_id": "3a91cf82-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
    "blogger_id": "46547c86-b171-480a-85d7-70a856164c95",
    "is_reactivation": false,
    "created_at": "2026-08-18T12:34:56Z",
    "started_at": "2026-08-18T12:34:57Z",
    "completed_at": "2026-08-18T12:35:20Z",
    "duration_seconds": 23,
    "result": {
      "tracked_account_id": "3a91cf82-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
      "blogger_id": "46547c86-b171-480a-85d7-70a856164c95",
      "platform": "instagram",
      "username": "bar.films",
      "is_active": true,
      "initial_videos_count": 10,
      "daily_videos_count": null,
      "campaign": null,
      "ecpm": null,
      "last_scraped_at": null
    }
  },
  "meta": {
    "request_id": "be6132b9-ab8b-4fe6-895c-1277f59a0616"
  }
}
```

### Status: `error`

The job failed. Inspect `data.error.code` and `data.error.message` for diagnostics.

```json
{
  "success": true,
  "data": {
    "job_id": "8f3a7e21-6c4f-4d3b-9c2a-1e0b8d7c6f5a",
    "status": "error",
    "platform": "instagram",
    "username": "bar.films",
    "created_at": "2026-08-18T12:34:56Z",
    "started_at": "2026-08-18T12:34:57Z",
    "completed_at": "2026-08-18T12:35:57Z",
    "duration_seconds": 60,
    "error": {
      "code": "PLATFORM_NOT_FOUND",
      "message": "Could not resolve channel 'bar.films' on instagram: 404."
    }
  },
  "meta": {
    "request_id": "be6132b9-ab8b-4fe6-895c-1277f59a0616"
  }
}
```

### Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `data.job_id` | `string` | Internal job UUID. |
| `data.status` | `string` | One of `queued`, `processing`, `succeeded`, `error`. |
| `data.platform` | `string` | Echoed platform from the originating `POST`. |
| `data.username` | `string` | Echoed username from the originating `POST`. |
| `data.created_at` | `string` | ISO 8601 timestamp when the job was enqueued. |
| `data.started_at` | `string \| null` | ISO 8601 timestamp when the consumer picked it up; `null` while `queued`. |
| `data.elapsed_seconds` | `number \| null` | Seconds since `started_at`; present while `processing`, `null` while `queued`. |
| `data.tracked_account_id` | `string \| null` | Tracked-account row UUID (present once `succeeded`). |
| `data.blogger_id` | `string \| null` | Blogger row UUID (present once `succeeded`). |
| `data.is_reactivation` | `boolean \| null` | `true` if the job reactivated a previously deactivated creator (present once `succeeded`). |
| `data.completed_at` | `string \| null` | ISO 8601 timestamp when the job reached a terminal state (`succeeded` / `error`). |
| `data.duration_seconds` | `number \| null` | Wall-clock seconds between `started_at` and `completed_at`. |
| `data.result` | `object \| null` | Tracked-creator snapshot — present only when `status: "succeeded"`. |
| `data.error` | `object \| null` | `{ code, message }` — present only when `status: "error"`. |
| `meta.request_id` | `string` | Unique request identifier (also echoed in error responses). |

## ⚠️ Error Responses

All errors share the standard envelope `{ success: false, error: { code, message, request_id, ... } }`. The table below lists every documented `code` and the conditions that trigger it.

| Status | `code` | Trigger | Additional `error` fields |
|--------|--------|---------|---------------------------|
| `404` | `NOT_FOUND` | The `job_id` doesn't exist **or** belongs to a different organization (Stripe-parity — both cases return `404` so the endpoint never leaks another org's job) | — |
| `410` | `JOB_TIMEOUT` | The job exceeded `JOB_TIMEOUT` (default `1h`); the hourly stuck-jobs sweeper marked it terminal. Re-issue `POST /v1/bloggers/track` to retry. | — |

## 🔧 Features

### Polling-Only Delivery
- No webhook delivery — clients must poll. The originating `POST /v1/bloggers/track` returns `Retry-After: 1` (1 second) as a starting hint.
- All terminal and non-terminal states share the same `200 OK` envelope; discriminate on `data.status`.

### Recommended Backoff
- Exponential backoff with jitter: `1s → 2s → 4s → 8s → max 30s`.
- Stop polling once `status IN ('succeeded','error')` or the response is `410`.
- Suggested max wait: **5 minutes**. Past that, assume the job is stuck and contact support with the `job_id`.

### Org Isolation
- Jobs are scoped to the authenticated org's API key.
- A `job_id` from another org is indistinguishable from a non-existent id (both → `404`).

### Sweeper Recovery
- Jobs stuck in `processing` past `JOB_TIMEOUT` are auto-marked `error / JOB_TIMEOUT` by the hourly stuck-jobs sweeper cron.
- Clients should re-issue `POST /v1/bloggers/track` after observing `410` (or after a stuck-job timeout in their own polling loop).

## 🎯 Use Cases

- **Async workflow polling** after `POST /v1/bloggers/track` — primary purpose.
- **CI/CD integration** — poll until `succeeded` before triggering downstream actions that depend on stats.
- **Dashboard-side polling** — same shape as programmatic API polling; the dashboard's "Add creator" UI uses the same pipeline.

## 📈 What Happens Next?

After a `succeeded` response:
1. Use `data.result.blogger_id` and `data.result.tracked_account_id` for downstream calls.
2. Initial video backfill is enqueued in parallel; expect stats to populate within 2–5 minutes.
3. Poll `GET /v1/videos/stats?blogger_username={username}` (optionally filtered) once backfill is complete.

After an `error` response:
- Inspect `data.error.code`. Common codes: `PLATFORM_NOT_FOUND`, `EXTERNAL_API_ERROR`, `JOB_TIMEOUT`.
- Re-issue `POST /v1/bloggers/track` after addressing the underlying cause (correct username/id, valid platform, etc.).

## 📊 Rate Limits

- **Requests**: 60 requests per minute per API key (same envelope as other paid endpoints).
- **No credits consumed** — polling is free.

---

**Related**: [Track Creator →](./track) | [Blogger Info →](./info)
