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

# Operations

> Run OAuth writes, confirm credit use, and track operation status.

## Overview

OAuth writes run as durable operations. The API rechecks scopes, accepted admin membership, operation fields and current state on every call. Every execution needs a stable `idempotency_key`; completed retry records are kept for **30 days**.

| Write | Protocol |
| - | - |
| [Create Event](/api-reference/create-event), [Rename Event](/api-reference/rename-event), [Event Settings](/api-reference/event-settings), [Photo Sources](/api-reference/photo-sources), [Participant Search](/api-reference/participant-search), [Import](/api-reference/pipeline/photos/import), [Go Live](/api-reference/pipeline/photos/go-live), [Unpublish](/api-reference/pipeline/photos/unpublish) | Direct: one call with `mode: "execute"` and `idempotency_key`. |
| [Process Photos](/api-reference/pipeline/photos/process) | Reviewed: spends credits, so it needs a preview and user confirmation. |

Any write accepts `mode: "preview"` as a dry run that returns `proposed` and `consequences` without changing anything.

## Direct Writes

Send the operation fields with `mode: "execute"` and a stable `idempotency_key`. No `review_token` or `confirmed` is needed. Save the returned `operation_id`.

[Go Live](/api-reference/pipeline/photos/go-live) makes the event live for participants; tell the user before calling it. [Unpublish](/api-reference/pipeline/photos/unpublish) moves it back to draft.

## Reviewed Writes

> [!WARNING]
>
> `confirmed: true` is the client's assertion that the user approved the exact preview, including the credits it uses. It is not backend proof of human presence.

1. Submit the operation fields with `mode: "preview"`.
2. Show the user `proposed.effective_count` and `proposed.credits_consumed`.
3. After confirmation, repeat the same fields with `mode: "execute"`, the returned `review_token`, `confirmed: true` and a stable `idempotency_key`.
4. Reviews expire after **15 minutes**. A changed payload or event state requires a fresh preview.

## Retries

After an ambiguous timeout, query the recorded operation or repeat the identical execution with the same retry key. Do not invent a new key. Reusing a key for a different action returns `409`. Pending or queued means accepted, not completed. `dispatch_unknown` requires status checks and support review before another operation; do not infer failure from an uncertain dispatch.

## Endpoint

```
GET /api/v1/ext/{org_id}/operations/{operation_id}
```

OAuth only, requiring `events:read` and accepted membership. Operations are scoped to the signed-in principal as well as the organiser: another user cannot read them even with the same organisation membership.

## Path Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `org_id` | integer | Yes | Authorised organiser. |
| `operation_id` | UUID | Yes | Durable ID returned by execution. |

## Example Request

```bash cURL theme={null}
curl -i -X GET \
  -H "Authorization: Bearer <oauth_access_token>" \
  "https://api.9pic.ai/api/v1/ext/903/operations/ddc661a7-8861-4793-9437-af42a82d12f8"
```

```python Python theme={null}
import requests

response = requests.get(
    "https://api.9pic.ai/api/v1/ext/903/operations/ddc661a7-8861-4793-9437-af42a82d12f8",
    headers={"Authorization": "Bearer <oauth_access_token>"},
)
print(response.status_code, response.json())
```

```javascript JavaScript theme={null}
const response = await fetch("https://api.9pic.ai/api/v1/ext/903/operations/ddc661a7-8861-4793-9437-af42a82d12f8", {
  method: "GET",
  headers: { "Authorization": "Bearer <oauth_access_token>" },
});
console.log(await response.json());
```

## Example Response

```json theme={null}
{
  "responseType": "success",
  "message": "Records",
  "data": {
    "mode": "execute",
    "operation": "process_photos",
    "org_id": 903,
    "event_id": 456,
    "review_token": null,
    "expires_at": null,
    "proposed": {
      "requested_count": 10,
      "effective_count": 10,
      "credits_consumed": 10
    },
    "consequences": [
      "Processes up to 10 photos and reserves 10 credits."
    ],
    "operation_id": "ddc661a7-8861-4793-9437-af42a82d12f8",
    "status": "pending",
    "result": {
      "event_id": 456
    }
  }
}
```

## Operation States

| State | Meaning |
| - | - |
| `preview` | Review available; no operation executed. |
| `pending` | Accepted and awaiting dispatch. |
| `queued` | Dispatched for background work. |
| `running` | Work has started. |
| `completed` | Operation completed. |
| `failed` | Recorded failure; inspect status before retrying. |
| `dispatch_unknown` | Delivery outcome is uncertain; requires reconciliation. |

## Response Models

[ReviewedWriteRequest](/api-reference/models/workflow#reviewedwriterequest) and [GuardedOperationData](/api-reference/models/workflow#guardedoperationdata).

## Error Responses

| Status | Meaning |
| - | - |
| `400` / `422` | Invalid input, missing `idempotency_key`, or missing review fields for Process Photos. |
| `401` | Invalid or expired access token. |
| `402` | Not enough credits for Process Photos; the message lists the plan's options. |
| `403` | Missing scopes, membership, admin role, or a plan limit (see [Plan](/api-reference/plan)). |
| `404` | Event configuration or caller-owned operation not found. |
| `409` | Expired or mismatched review, retry key reused for another action, changed state, duplicate source, or active work. |
| `503` | Organiser writes are temporarily unavailable. |

See [Errors](/api-reference/errors) and [Conventions](/api-reference/conventions).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.