Metadata-Version: 2.4
Name: aeyvision-cloud
Version: 1.0.0
Summary: AEY Cloud: computer vision models and rules over images, video and cameras.
License: MIT
Project-URL: Homepage, https://cloud.aeyvision.com
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# AEY Cloud — Python SDK

Computer vision over images, video and cameras. Run a model from the catalogue
— detection, segmentation, keypoints, embeddings — and get its own output back,
or describe what matters as rules and get back only the events you asked for.

Paid from prepaid credits: every run and event records exactly what it cost.
Queue time is reported and never charged, and failed runs are free.

## Install

```
pip install https://cloud.aeyvision.com/sdk/python/latest.tar.gz
```

No dependencies — standard library only. An SDK that drags in an HTTP
stack is an SDK that eventually conflicts with the one your application
already chose.

## A key

Create one in the dashboard at https://cloud.aeyvision.com, or with
`POST /v1/keys`. The client reads `AEYVISION_API_KEY` from the environment if
you do not pass one — the form that works in a container without putting a
credential in an argument list.

```python
from aeyvision import AeyVision

cv = AeyVision(api_key="aey_...")
```

## What it can do

```python
from aeyvision import AeyVision

cv = AeyVision()  # reads AEYVISION_API_KEY
```

**Browse the catalogue.** `tasks` explains what each capability returns and
which parameters it honours; `models` is the menu.

```python
catalogue = cv.list_catalogue()

for task in catalogue["tasks"].values():
    print(task["label"], "—", task["summary"])

for model in catalogue["models"]:
    if model["available"]:
        print(model["id"], model["task"], model["licence"])
```

**Detect objects in a clip.**

```python
answer = cv.create_run(
    model="rfdetr-nano",
    video_url="https://example.com/forecourt.mp4",
    params={"classes": ["person", "car"], "confidence": 0.4, "sample_fps": 2},
)

print(answer["run"]["cost"]["priceUsd"], "for", answer["run"]["frames"], "frames")

for frame in answer["output"]["frames"]:
    for found in frame.get("detections", []):
        print(frame["index"], found["label"], round(found["confidence"], 2), found["box"])
```

**Ask a question instead of reading boxes.** Rules turn detections into
events; `level` is `None` when nothing you asked about happened.

```python
verdict = cv.analyse(
    video_url="https://example.com/yard.mp4",
    areas=[{"name": "driveway", "points": [[0, 0.5], [1, 0.5], [1, 1], [0, 1]]}],
    rules=[{"when": {"object": "car", "state": "moving", "area": "driveway"}, "raise": "alert"}],
)

if verdict["level"] == "alert":
    for event in verdict["events"]:
        print(event["message"])
```

**Segment things with a text prompt**, using SAM 3.

```python
cv.create_run(
    model="sam3",
    video_url="https://example.com/site.mp4",
    params={"prompt": ["forklift", "hard hat", "spilled liquid"]},
)
```

**Redact footage** before you share it: everyone blurred but the people you
leave visible. The pixels are destroyed, not drawn over, and audio is removed.

```python
import time

with open("door.mp4", "rb") as handle:
    upload = cv.create_upload(handle.read(), filename="door.mp4")["upload"]

redaction = cv.create_redaction(upload_id=upload["id"])["redaction"]
while redaction["status"] == "analysing":
    time.sleep(5)
    redaction = cv.get_redaction(redaction["id"])["redaction"]

# Every person and vehicle, as a track. Leave the first person visible.
tracks = cv.get_redaction_analysis(redaction["id"])["analysis"]["tracks"]
keep = [t["id"] for t in tracks if t["label"] == "face"][:1]
cv.render_redaction(redaction["id"], review={"keep": keep, "boxes": []})

while redaction["status"] != "ready":
    time.sleep(5)
    redaction = cv.get_redaction(redaction["id"])["redaction"]

with open("door-redacted.mp4", "wb") as handle:
    handle.write(cv.get_redaction_video(redaction["id"]))
```

## Processing price

```python
cost = answer["run"]["cost"]

print(cost["seconds"], "s on", cost["gpu"])
print("price per hour", cost["pricePerHour"])
print("charged", cost["priceUsd"])
print("queued ", cost["queuedSeconds"], "s — not billed")
```

Full logs of what the container did — the same ones the console renders:

```python
log = cv.get_run_logs(answer["run"]["id"])

for line in log["lines"]:
    print(line["t"], line["level"], line["message"], line.get("fields", ""))

for stage in log["stages"]:
    print(stage["name"], stage["ms"], "ms")
```

And spend across the account:

```python
usage = cv.get_usage(days=30)
print(usage["total"]["runs"], "runs,", usage["total"]["priceUsd"], "USD")
```

## Watch out for

Two-stage models such as `object-search` run their second stage **once per
detected object**, not once per frame. A busy car park costs meaningfully more
than an empty drive. The catalogue flags these
with `scales_with_objects`, before the run rather than on the invoice.

`sample_fps` is the biggest lever on cost. Two frames a second answers most
questions; ten costs five times as much to answer them slightly better.

## Every method

### Training

| Method | Endpoint | What it does |
| --- | --- | --- |
| `add_training_image` | `POST /v1/training/datasets/{id}/images` | Add an image and its boxes. JSON, or the image as the body with ?split= and ?annotations= (URL-encoded JSON). |
| `cancel_training_job` | `POST /v1/training/jobs/{id}/cancel` | Stop a job. Charged for the time it ran; the rest of the hold is returned. |
| `create_training_dataset` | `POST /v1/training/datasets` | Create a trainingDataset. |
| `create_training_job` | `POST /v1/training/jobs` | Fine-tune RF-DETR on a dataset. Sets aside the most it can cost, and charges for the time it uses. |
| `delete_training_dataset` | `DELETE /v1/training/datasets/{id}` | Delete a trainingDataset. |
| `delete_training_image` | `DELETE /v1/training/datasets/{id}/images/{imageId}` | Remove an image from a dataset. |
| `delete_training_job` | `DELETE /v1/training/jobs/{id}` | Delete a finished job. Its model stays. |
| `estimate_training` | `POST /v1/training/estimate` | What a training job would set aside and most likely cost, without starting it. |
| `get_training_dataset` | `GET /v1/training/datasets/{id}` | Get one trainingDataset. |
| `get_training_image_file` | `GET /v1/training/datasets/{id}/images/{imageId}/file` | The image itself. |
| `get_training_job` | `GET /v1/training/jobs/{id}` | A job's progress, metrics, charge and model. |
| `list_training_datasets` | `GET /v1/training/datasets` | List trainingDatasets owned by the account. |
| `list_training_images` | `GET /v1/training/datasets/{id}/images` | Every image in a dataset, with its boxes. |
| `list_training_jobs` | `GET /v1/training/jobs` | Training jobs, newest first. |
| `update_training_dataset` | `PATCH /v1/training/datasets/{id}` | Update a trainingDataset. |
| `update_training_image` | `PATCH /v1/training/datasets/{id}/images/{imageId}` | Replace an image's boxes, or move it between train and valid. |

### Rules

| Method | Endpoint | What it does |
| --- | --- | --- |
| `analyse` | `POST /v1/analyse` | Grade media against rules sent with the request. Nothing saved unless asked. |
| `analyse_with_detector` | `POST /v1/detectors/{id}/analyse` | Grade media against a saved detector. |
| `create_detector` | `POST /v1/detectors` | Create a detector. |
| `delete_detector` | `DELETE /v1/detectors/{id}` | Delete a detector and everything it recorded. |
| `get_detector` | `GET /v1/detectors/{id}` | One detector, with its twenty most recent events. |
| `get_event` | `GET /v1/events/{id}` | One event in full. |
| `get_event_clip` | `GET /v1/events/{id}/clip` | The annotated clip for an event. |
| `get_event_image` | `GET /v1/events/{id}/image` | The annotated frame for an event. |
| `list_detectors` | `GET /v1/detectors` | Every detector on this account. |
| `list_events` | `GET /v1/events` | Everything analysed, including the calls that raised nothing. |
| `list_models` | `GET /v1/models` | The rules product's detector variants, and every class a rule may name. |
| `update_detector` | `PATCH /v1/detectors/{id}` | Change a detector. Only the fields you send. |

### Analytics

| Method | Endpoint | What it does |
| --- | --- | --- |
| `append_analytics_event` | `POST /v1/analytics/events` | Record an analytics point. |
| `query_analytics` | `POST /v1/analytics/query` | Query recalled analytics points with a structured filter. |
| `query_analytics_events` | `GET /v1/analytics/events` | Recall analytics points stored for the account. |

### Sites

| Method | Endpoint | What it does |
| --- | --- | --- |
| `check_camera_health` | `POST /v1/cameras/{id}/health` | Inspect what this camera is actually showing. |
| `create_camera` | `POST /v1/cameras` | Create a camera. |
| `create_customer` | `POST /v1/customers` | Create a customer. |
| `create_site` | `POST /v1/sites` | Create a site. |
| `delete_camera` | `DELETE /v1/cameras/{id}` | Delete a camera. |
| `delete_customer` | `DELETE /v1/customers/{id}` | Delete a customer. |
| `delete_site` | `DELETE /v1/sites/{id}` | Delete a site. |
| `dial_camera` | `POST /v1/cameras/{id}/dial` | Dial this camera's RTSP stream and take a frame. |
| `get_camera` | `GET /v1/cameras/{id}` | Get one camera. |
| `get_camera_false_alarms` | `GET /v1/cameras/{id}/false-alarms` | Where this camera's false alarms come from, using outcomes stored on its events. |
| `get_camera_snapshot` | `GET /v1/cameras/{id}/snapshot` | The most recent frame taken from this camera. |
| `get_camera_zone_suggestions` | `GET /v1/cameras/{id}/zones/suggestions` | The last scene reading stored for this camera, without running SAM 3. |
| `get_customer` | `GET /v1/customers/{id}` | Get one customer. |
| `get_site` | `GET /v1/sites/{id}` | Get one site. |
| `list_camera_health` | `GET /v1/cameras/{id}/health` | This camera's health history, newest first. |
| `list_cameras` | `GET /v1/cameras` | List cameras owned by the account. |
| `list_customers` | `GET /v1/customers` | List customers owned by the account. |
| `list_customer_sites` | `GET /v1/customers/{customerId}/sites` | List sites belonging to one customer. |
| `list_site_cameras` | `GET /v1/sites/{siteId}/cameras` | List cameras belonging to one site. |
| `list_site_health` | `GET /v1/sites/{siteId}/health` | Every camera at this site, with its latest health answer. |
| `list_sites` | `GET /v1/sites` | List sites owned by the account. |
| `report_camera_false_alarms` | `POST /v1/cameras/{id}/false-alarms` | Where this camera's false alarms come from, and which zones would stop them. |
| `suggest_camera_zones` | `POST /v1/cameras/{id}/zones/suggest` | Read the camera's scene into zone-shaped regions with SAM 3. |
| `suggest_zones` | `POST /v1/zones/suggest` | Read any frame into zone-shaped regions with SAM 3. Nothing is stored. |
| `update_camera` | `PATCH /v1/cameras/{id}` | Update a camera. |
| `update_customer` | `PATCH /v1/customers/{id}` | Update a customer. |
| `update_site` | `PATCH /v1/sites/{id}` | Update a site. |

### Object search

| Method | Endpoint | What it does |
| --- | --- | --- |
| `clear_site_objects` | `DELETE /v1/sites/{siteId}/objects` | Delete this site's object index, and the crops with it. |
| `get_object` | `GET /v1/objects/{id}` | One kept object crop. |
| `get_object_image` | `GET /v1/objects/{id}/image` | The crop itself, as a JPEG. |
| `list_site_objects` | `GET /v1/sites/{siteId}/objects` | List the object crops this site has kept. |
| `search_site_objects` | `POST /v1/sites/{siteId}/search` | Find the objects this site has already seen that look like this one. |

### Account

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_checkout_session` | `POST /v1/billing/checkout-session` | Create a hosted Stripe Checkout Session for credits. |
| `create_free_trial_session` | `POST /v1/billing/free-trial-session` | Start claiming the free credits: a Stripe Checkout Session in setup mode. |
| `create_key` | `POST /v1/keys` | Mint a key. The secret is returned once and never again. |
| `get_billing` | `GET /v1/billing` | Credit balance, billing fee minimum, and account entitlement. |
| `get_brand` | `GET /v1/brand` | What the console is called on the domain this request was made to. |
| `get_invite` | `GET /v1/invites/{token}` | Who a sub-account invitation is from, and for which address. |
| `health` | `GET /health` | Liveness. Unauthenticated. |
| `invite_sub_account` | `POST /v1/sub-accounts/invites` | Invite an email address to become a sub-account. |
| `list_keys` | `GET /v1/keys` | Every key on this account. Never the secrets. |
| `list_sub_accounts` | `GET /v1/sub-accounts` | Your sub-accounts, and invitations not yet accepted. |
| `me` | `GET /v1/me` | The authenticated account, and how it authenticated. |
| `revoke_key` | `DELETE /v1/keys/{id}` | Revoke a key. Immediate. |
| `revoke_sub_account_invite` | `DELETE /v1/sub-accounts/invites/{id}` | Withdraw an invitation that has not been accepted. |
| `update_sub_account` | `PATCH /v1/sub-accounts/{id}` | Change a sub-account's customer or monthly cap, or suspend it. |

### Email

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_email_destination` | `POST /v1/email/destinations` | Create a emailDestination. |
| `delete_email_destination` | `DELETE /v1/email/destinations/{id}` | Delete a emailDestination. |
| `get_email_destination` | `GET /v1/email/destinations/{id}` | Get one emailDestination. |
| `list_email_destinations` | `GET /v1/email/destinations` | List emailDestinations owned by the account. |
| `update_email_destination` | `PATCH /v1/email/destinations/{id}` | Update a emailDestination. |

### Models

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_model_registration` | `POST /v1/model-registrations` | Create a modelRegistration. |
| `delete_model_registration` | `DELETE /v1/model-registrations/{id}` | Delete a modelRegistration. |
| `get_model_registration` | `GET /v1/model-registrations/{id}` | Get one modelRegistration. |
| `list_model_registrations` | `GET /v1/model-registrations` | List modelRegistrations owned by the account. |
| `put_model_artifact` | `PUT /v1/model-registrations/{id}/artifact` | Upload the ONNX file for a model registration. |
| `update_model_registration` | `PATCH /v1/model-registrations/{id}` | Update a modelRegistration. |

### Redaction

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_redaction` | `POST /v1/redactions` | Redact an upload: find every face and number plate in it, to review. |
| `delete_redaction` | `DELETE /v1/redactions/{id}` | Delete a redaction and its original footage. Its runs, and the redacted video, stay under Runs. |
| `delete_redaction_footage` | `DELETE /v1/redactions/{id}/footage` | Delete the original footage and keep the redacted video. The redaction can no longer be edited. |
| `get_redaction` | `GET /v1/redactions/{id}` | One redaction, brought up to date with the run it is waiting on. |
| `get_redaction_analysis` | `GET /v1/redactions/{id}/analysis` | Every person and vehicle found, as tracks of normalised boxes over time. |
| `get_redaction_source` | `GET /v1/redactions/{id}/source` | The original footage. Honours Range, so a video player can seek. |
| `get_redaction_video` | `GET /v1/redactions/{id}/video` | The redacted video, once `status` is `ready`. Honours Range. |
| `list_redactions` | `GET /v1/redactions` | Every redaction on this account, newest first. |
| `render_redaction` | `POST /v1/redactions/{id}/render` | Make the redacted video from a review. |
| `retry_redaction_analysis` | `POST /v1/redactions/{id}/analyse` | Analyse the footage again, after an analysis failed. |
| `update_redaction` | `PATCH /v1/redactions/{id}` | Rename a redaction, or save a review in progress as its draft. |

### Platform

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_run` | `POST /v1/runs` | Run one model over one video or image. |
| `create_upload` | `POST /v1/uploads` | Upload footage for a footage job, up to 4 GB. |
| `delete_upload` | `DELETE /v1/uploads/{id}` | Delete an upload: the footage itself, not just the record. |
| `get_customer_usage` | `GET /v1/usage/customers` | Spend split by your own customers, for rebilling. |
| `get_pricing` | `GET /v1/pricing` | Customer processing price per hour. |
| `get_rates` | `GET /v1/rates` | Public list prices per processing hour. |
| `get_run` | `GET /v1/runs/{id}` | One run: its status, its counts and exactly what it cost. |
| `get_run_clip` | `GET /v1/runs/{id}/clip` | The annotated clip, when the run produced one. |
| `get_run_logs` | `GET /v1/runs/{id}/logs` | Every line the container logged for this run. |
| `get_run_output` | `GET /v1/runs/{id}/output` | The model's full output — every frame, every detection. |
| `get_upload` | `GET /v1/uploads/{id}` | One upload. |
| `get_upload_file` | `GET /v1/uploads/{id}/file` | The uploaded footage itself. Honours Range, so a video player can seek. |
| `get_usage` | `GET /v1/usage` | Spend, by day and by model. |
| `list_catalogue` | `GET /v1/catalogue` | Every model this platform can run, and what each capability returns. |
| `list_runs` | `GET /v1/runs` | Run history, newest first. |
| `list_uploads` | `GET /v1/uploads` | Footage this account has uploaded and not yet deleted. |

### Scripts

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_script` | `POST /v1/scripts` | Create a script. |
| `delete_script` | `DELETE /v1/scripts/{id}` | Delete a script. |
| `get_script` | `GET /v1/scripts/{id}` | Get one script. |
| `list_scripts` | `GET /v1/scripts` | List scripts owned by the account. |
| `update_script` | `PATCH /v1/scripts/{id}` | Update a script. |
| `write_script` | `POST /v1/scripts/ai-write` | Generate a reviewable Python video handler from a behavior brief. |

### UI Components

| Method | Endpoint | What it does |
| --- | --- | --- |
| `create_ui_component` | `POST /v1/ui-components` | Create a uiComponent. |
| `delete_ui_component` | `DELETE /v1/ui-components/{id}` | Delete a uiComponent. |
| `get_ui_component` | `GET /v1/ui-components/{id}` | Get one uiComponent. |
| `list_ui_components` | `GET /v1/ui-components` | List uiComponents owned by the account. |
| `update_ui_component` | `PATCH /v1/ui-components/{id}` | Update a uiComponent. |

### Events

| Method | Endpoint | What it does |
| --- | --- | --- |
| `list_audit` | `GET /v1/audit` | Query the account audit log. |
| `set_event_outcome` | `POST /v1/events/{id}/outcome` | Record whether an event was a false alarm or a real one. |


## Platform resources

The package also includes a typed `PlatformClient` for the domain resource
contract: sites, cameras, models, scripts, events, training jobs and runs.
These routes are configurable so a customer can proxy them through a different
gateway without changing the generated client.

```python
from aeyvision import PlatformClient

platform = PlatformClient(api_key="aey_...")
sites = platform.list_sites(limit=25)
```

For TypeScript, import `PlatformClient` from `@aeyvision/sdk`. Both clients
default to `/v1/customers`, `/v1/sites`, `/v1/cameras`,
`/v1/model-registrations`, `/v1/scripts`, `/v1/ui-components`,
`/v1/events`, `/v1/training/jobs` and `/v1/runs`; pass route overrides
when your gateway uses a different mount.


## Errors

Anything but a 2xx raises `AeyVisionError`, carrying `status` and — when the
rules API rejected a configuration — `problems`, the per-rule complaints. Both
are attributes rather than prose in the message, because code that retries on
429 and gives up on 400 should not have to parse English.

Requests are retried on 429 and 5xx with exponential backoff, twice by default.
Nothing else is retried: a 400 will be a 400 again, and a run that failed on the
GPU has already cost real money — repeating it spends more of it for the same
answer.

## Licences

Every catalogue entry carries its licence, and `commercial` is a field. An
entry whose weights you cannot ship inside a closed product is marked
`commercial: false` with a `licence_note` saying why, and the warning rides
on every response that used one. Read `output.warnings` before
you build on a result.

## This package is generated

Everything here comes from `functions/api/src/openapi.ts` by way of
`scripts/generate-sdk.mjs`. Run `npm run sdk` to rebuild it. Editing it by hand
is how a generated client starts lying about the API it describes.
