Skip to API documentation
ceresiavision
CERESIA VISION / API

Ceres Public API documentation (2.0.0 (v1))

Download OpenAPI specification:Download

Integration guide

Use the Ceres public REST API v2 to capture images, run analyses and read results. All examples use http://localhost:8000; replace it with your installation URL. The API does not require authentication. public identifies the integration contract, rather than an access level. Use the versioned /api/v2/public/ routes.

Interactive documentation is available at /api/v2/public/docs/, ReDoc at /api/v2/public/redoc/, and the OpenAPI document at /api/v2/public/schema/.

1. Discover cameras and varieties

CERES_URL=http://localhost:8000
curl --fail-with-body "$CERES_URL/api/v2/public/cameras/"
curl --fail-with-body "$CERES_URL/api/v2/public/commodities/"
curl --fail-with-body "$CERES_URL/api/v2/public/features/"

The catalogs return arrays. Cameras include id, name and capture_mode (discrete or continuous). Varieties include id, name and display_name. Features include id, name, display_name and api_name. The optional api_name is used as dataset[].feature in public v1 and v2 responses; when it is empty, the dataset keeps using the feature id. display_name remains a human-facing label. Always use values returned by your installation; IDs and names below are examples. Camera installation and configuration are maintenance operations.

Versionless public routes inherit v1 behavior, including API Names. The legacy v2 actions /analyses/list_no_frontend/ and /analyses/create_no_frontend/ continue returning feature IDs. Use the documented endpoints to receive API Names.

Operation Variety parameter Camera parameter
POST /analyses/ commodity: existing name camera_id: integer ID
PATCH /analyses/{id}/ commodity: existing name, optional Not accepted
POST /analysis-collections/ commodity: integer ID Not needed
PATCH /analysis-collections/{id}/ commodity: integer ID, optional Not needed
POST /analysis-collections/{id}/analyses/ Inherited from the collection camera_id: integer ID
POST /continuous-runs/ commodity: integer ID when creating a collection camera_id: integer ID

camera_id may be omitted when there is exactly one installed camera. Multiple cameras require explicit selection; zero cameras is an error. A continuous run requires a camera whose capture_mode is continuous.

2. Create and retrieve a single analysis

This operation captures a new photo, performs segmentation synchronously and creates a new collection containing the analysis. It returns HTTP 201 with the result, including id, collection, ticket, commodity, dataset, ISC, IRC, calibres, photo and segmented_photo.

curl --fail-with-body --max-time 180 \
  -X POST "$CERES_URL/api/v2/public/analyses/" \
  -H 'Content-Type: application/json' \
  -d '{"ticket": 12345, "commodity": "Example variety", "camera_id": 1}'

# Replace 42 with the id returned by the POST.
curl --fail-with-body "$CERES_URL/api/v2/public/analyses/42/"

The top-level creation requires an integer ticket and a variety name. Its commodity response is also the name. Collection endpoints instead return commodity as an object containing id and name.

3. Group captures in a collection

A collection groups analyses with a shared ticket, variety and extra_data. A ticket can identify multiple collections, including different varieties. Create a collection first and keep its ID when recovery after a lost response matters. Creation of a collection does not capture an image.

curl --fail-with-body -X POST "$CERES_URL/api/v2/public/analysis-collections/" \
  -H 'Content-Type: application/json' \
  -d '{"ticket": 12345, "commodity": 7, "extra_data": {"batch": "LOT-2026-001"}}'

# Replace 10 with the collection id returned above.
curl --fail-with-body --max-time 180 \
  -X POST "$CERES_URL/api/v2/public/analysis-collections/10/analyses/" \
  -H 'Content-Type: application/json' -d '{"camera_id": 1}'

curl --fail-with-body "$CERES_URL/api/v2/public/analysis-collections/10/?include_analyses=false"
curl --fail-with-body "$CERES_URL/api/v2/public/analysis-collections/10/analyses/?page=1&page_size=50"

Collection creation permits a missing or null ticket. extra_data is metadata owned by the collection and supplied by the client, not a raw segmentation response. Updating ticket, commodity or extra_data through an analysis PATCH updates its collection and therefore affects every analysis in that collection. Collection PATCH has the same shared effect. Deleting a collection deletes its linked analyses; deleting an individual analysis removes that analysis.

4. Start, monitor and stop continuous analysis

Capture and processing are asynchronous on the server. HTTP 201 means a run was created and capture was scheduled; it does not mean results are ready. With multiple installed cameras, always send the selected camera_id. The collection groups results; the camera is selected for each run, not when creating an empty collection.

Start with a new collection

First list cameras and varieties and choose their IDs. Select a camera whose capture_mode is continuous; do not assume the first camera in the catalog.

curl --fail-with-body "$CERES_URL/api/v2/public/cameras/"
curl --fail-with-body "$CERES_URL/api/v2/public/commodities/"

# Example: camera 2 and variety 7 were selected from these catalogs.
curl --fail-with-body -X POST "$CERES_URL/api/v2/public/continuous-runs/" \
  -H 'Content-Type: application/json' \
  -d '{"camera_id": 2, "commodity": 7, "ticket": 12345, "extra_data": {"batch": "LOT-2026-001"}}'

Omitting collection creates a new collection automatically, using the variety ID, optional ticket and optional metadata. Save both returned identifiers: id identifies the run; collection identifies the collection holding its results. For example, a response containing "id": 5, "collection": 10 means subsequent run actions use 5, while collection queries use 10.

Start with an existing collection

Alternatively, create an empty collection first using POST /analysis-collections/ as shown in section 3, or reuse an existing one:

curl --fail-with-body -X POST "$CERES_URL/api/v2/public/continuous-runs/" \
  -H 'Content-Type: application/json' \
  -d '{"collection": 10, "camera_id": 2}'

This adds results to collection 10 without deleting its existing analyses. Its ticket, variety and metadata are reused; omit commodity, ticket and extra_data from this request. A camera can have only one reserved continuous run. Use available_actions to decide which action to offer; state can change between reading it and performing an action.

Stop capture manually or at the configured limit

# Replace 5 with the returned run id. Poll this resource every two seconds.
curl --fail-with-body "$CERES_URL/api/v2/public/continuous-runs/5/"
curl --fail-with-body -X POST "$CERES_URL/api/v2/public/continuous-runs/5/pause/"
curl --fail-with-body -X POST "$CERES_URL/api/v2/public/continuous-runs/5/resume/"
curl --fail-with-body -X POST "$CERES_URL/api/v2/public/continuous-runs/5/stop/"

Capture also ends automatically at the installation's continuous_analysis_limit (default 300 captured frames per run). An administrator configures this in Django Admin. This is a frame-count limit, not a duration or a guarantee of 300 successful analyses. Resuming the same run does not reset its captured-frame count. The limit is not a request parameter; ask the installation administrator for the configured value. Reaching it ends capture, then drains accepted work. Queue limits and repeated processing failures can instead pause capture; inspect status and available_actions rather than assuming every inactive run finished.

Wait for processing before reading the aggregate

Status Meaning
running Capture is active.
degraded Capture continues despite recent processing failures.
paused Capture is paused; resume or stop the same run.
stopping A stop was requested; capture is shutting down.
draining Capture has ended; accepted frames are still being processed.
stopped Every accepted frame has finished successfully or failed.
failed Capture encountered an infrastructure failure; inspect available actions.

stop returning HTTP 200 does not mean processing has finished. Continue polling until status is stopped and pending_frames is zero before reading final results. paused and failed can still have pending work. Resume keeps the same run and collection; failed frames are counted and are not automatically reprocessed. A new run targeting the same collection can subsequently add results.

pending_frames = frames_captured - analyses_processed - failed_frames_count. It includes frames waiting and executing. frames_processing counts executing frames and is already included in pending_frames; do not add them together. Inspect failed_frames_count even when the run is stopped. Public REST polling is supported; no public WebSocket integration is required.

For example, a stop response may contain "status": "draining", "pending_frames": 4. Keep polling GET /continuous-runs/5/. Once it returns "status": "stopped", "pending_frames": 0, read the collection aggregate:

# Run 5 has finished draining; collection 10 is returned in that run's response.
curl --fail-with-body "$CERES_URL/api/v2/public/analysis-collections/10/?include_analyses=false"

# Optional: aggregate all collections sharing ticket 12345.
curl --fail-with-body "$CERES_URL/api/v2/public/analyses/aggregate/?ticket=12345"

The collection detail already includes the aggregated dataset, indices, category and optional calibres; a separate aggregation POST is not needed. Results can be read while a run is active, but are still partial. Stopped means all accepted frames finished successfully or failed: check failed_frames_count and analyses_processed to assess completeness. For a reused collection, the aggregate includes its previous analyses too; a ticket aggregate can include additional collections. With zero successful analyses, a new collection has no measured aggregate. Another run adding results to that collection can change its aggregate again.

A complete Python example below handles camera selection, either collection mode, stop, bounded polling and the final collection aggregate. It never retries a creation POST automatically.

5. Read and filter results

curl --fail-with-body \
  "$CERES_URL/api/v2/public/analyses/?ticket=12345&collection=10&page=1&page_size=50&ordering=-created,-id"
curl --fail-with-body --get "$CERES_URL/api/v2/public/analyses/" \
  --data-urlencode 'created_after=2026-10-01T00:00:00Z' \
  --data-urlencode 'created_before=2026-10-31T23:59:59Z' \
  --data-urlencode 'page=1'
curl --fail-with-body "$CERES_URL/api/v2/public/analyses/aggregate/?ticket=12345"
Parameter for GET /analyses/ Meaning
ticket Exact integer ticket.
collection Exact positive collection ID.
created_after, created_before Inclusive ISO 8601 date/time bounds; use UTC Z or an explicit offset. Offset-free values use UTC.
ordering Comma-separated id, created, last_modified; prefix - for descending. Default: -created,-id. ID breaks ties.
page Positive page number; enables pagination.
page_size Integer from 1 to 1000; enables pagination; default is 50.

Filters are combined with AND and applied before pagination. Invalid parameters or reversed date bounds return HTTP 400. An out-of-range page returns HTTP 404. Follow next until it is null. Request pagination for normal integrations:

{"count": 0, "next": null, "previous": null, "results": []}

The example above represents an empty result set. Otherwise, results contains the analyses for that page. Unpaginated result limit: without page and page_size, the response is an array only when the filters match at most 1000 analyses, the same as the maximum page size. A request matching more than 1000 analyses returns HTTP 400 with code pagination_required, before loading the analysis results. This protects backend memory from unbounded history reads. No partial array is returned.

{
  "code": "pagination_required",
  "message": "Too many results. Use page or page_size to paginate this request.",
  "details": {
    "detail": "Too many results. Use page or page_size to paginate this request."
  }
}

Repeat the request with the same filters and page=1 (50 results by default), optionally setting page_size up to 1000, then follow next until it is null:

curl --fail-with-body \
  "$CERES_URL/api/v2/public/analyses/?ticket=12345&page=1&page_size=50"

Collection lists and collection analysis lists are always paginated. Pagination does not provide a snapshot while new results are being added.

Result interpretation

  • dataset contains feature names and numeric percentages. Each feature value is the configured api_name, or the feature ID when no API Name is configured. Use /features/ to discover IDs, API Names and human-facing display labels; these values and variety names are installation data.
  • Product and defect percentages use the useful analysis area, excluding background and indeterminate regions. Those excluded regions use total image area. The installation can configure area or weight mode; do not assume that every exposed percentage sums to 100, especially with derived features.
  • ISC and IRC are sanitary and harvest quality indices on a 0–100 scale. Null means unavailable, not a measured zero. Some nested analysis resources use the longer indice_sanitario_ceresia and indice_recogida_ceresia names.
  • Collection details expose aggregated results; /analyses/aggregate/?ticket=... aggregates all collections for the ticket. It accepts only the ticket filter. Aggregation uses combined feature data, rather than a simple average of displayed percentages. Caliber means are weighted by measured fruit count.
  • calibres contains millimeters or null. fruit_count: 0 is a measurement with zero fruit; null means no physical measurement is available. Histogram counts correspond to consecutive 1 mm intervals starting at start_mm. Optional configured_histogram groups those counts using installation settings.
  • Images can be absent, especially for continuous analyses. Detail images, when present, are base64 JPEG with a MIME type. They are compressed at quality 30. Missing images do not imply missing numerical results. Parse timestamps as ISO 8601; the installation uses UTC.

To save available original and segmented images together with their analysis results, use the collection download example below.

6. Errors, timeouts and recovery

Public v2 operation errors use an English envelope:

{
  "code": "validation_error",
  "message": "Invalid request parameters.",
  "details": {"commodity": ["This field is required."]}
}
HTTP Typical codes
400 validation_error, parse_error, camera_required, camera_not_found, no_cameras_available, camera_busy, camera_not_continuous, invalid_run_state
404 not_found (resource or page missing; no analyses for an aggregate ticket)
405 / 406 / 415 method_not_allowed, not_acceptable, unsupported_media_type
500 model_error, internal_error
503 camera_unavailable, model_unavailable

details contains field errors or useful context, or null. Server diagnostics are logged internally instead of exposing service responses. Reverse proxies can return their own error bodies; verify the content type before parsing JSON. v1 and private endpoints retain their existing error formats.

Camera HTTP calls have a 60-second timeout and model HTTP calls a 90-second timeout. Synchronous creation can include both calls. Examples use a 180-second client timeout; deployment proxies may impose different limits. These are timeout settings, not a guarantee of completion within a fixed wall-clock duration.

A connection failure, client timeout or lost response does not establish whether the operation executed. There is no idempotency key or duplicate suppression. Do not automatically retry creation POSTs. Even an error response does not guarantee that no capture or partial persistence occurred.

  1. Keep every returned collection, analysis and run ID.
  2. After an uncertain capture, inspect analyses in the known collection, or filter by ticket, collection and the time window. Results can still be changing.
  3. After an uncertain run start, read the known collection's latest_continuous_run and then retrieve that run. The latest run is a recovery hint, not a request ID.
  4. Read the current run and available_actions before repeating an action. A repeated resume, for example, can restart capture if state changed again.
  5. Decide explicitly whether to request another capture. Matching metadata or timestamps cannot identify the original request unequivocally. Reading an analysis row alone does not prove segmentation finished after a failed request.

GET requests can be retried with bounded backoff after transient failures. For example, wait 1, 2 and 4 seconds, then report the failure instead of retrying indefinitely. Keep routine run polling at two-second intervals.

Python example: capture inside a known collection

Install requests, then set CERES_URL, CERES_VARIETY and CERES_CAMERA_ID for your installation. Save the following as capture.py and run python capture.py. It performs one capture and never retries a POST automatically.

import json
import os

import requests

BASE_URL = os.environ.get("CERES_URL", "http://localhost:8000").rstrip("/")
VARIETY_NAME = os.environ["CERES_VARIETY"]
CAMERA_ID = int(os.environ["CERES_CAMERA_ID"])
TICKET = int(os.environ.get("CERES_TICKET", "12345"))


def request(method, resource, **kwargs):
    response = requests.request(
        method, f"{BASE_URL}/api/v2/public/{resource}",
        timeout=(5, 180), **kwargs,
    )
    if not response.ok:
        raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
    return response.json()


varieties = request("GET", "commodities/")
variety = next(item for item in varieties if item["name"] == VARIETY_NAME)
cameras = request("GET", "cameras/")
if not any(item["id"] == CAMERA_ID for item in cameras):
    raise ValueError("The selected camera is not installed.")

collection = request("POST", "analysis-collections/", json={
    "ticket": TICKET, "commodity": variety["id"],
    "extra_data": {"batch": "LOT-2026-001"},
})
print(f"Collection ID: {collection['id']}", flush=True)

try:
    analysis = request("POST", f"analysis-collections/{collection['id']}/analyses/",
                       json={"camera_id": CAMERA_ID})
except (requests.RequestException, RuntimeError):
    print(f"Capture outcome is uncertain. Inspect collection {collection['id']} before retrying.")
    raise

print(f"Analysis ID: {analysis['id']}", flush=True)
result = request("GET", f"analyses/{analysis['id']}/")
print(json.dumps(result, indent=2))

Python example: download collection images and results

Install requests with pip install requests. Set CERES_URL and CERES_COLLECTION_ID, save the following as download_collection.py, and run python download_collection.py:

export CERES_URL=http://localhost:8000
export CERES_COLLECTION_ID=10
python download_collection.py

The script follows the paginated analysis list and fetches each analysis detail. For each analysis with at least one available image, it saves the available images and resultados.json in a directory named after the analysis ID. The JSON contains the numerical results and other detail fields, replacing base64 image payloads with local file references and image metadata. It also saves the collection's aggregated results as resultados_agregados.json in the main collection directory. Analyses without images are skipped, even when their numerical results are available.

import base64
import json
import os
from pathlib import Path

import requests

# Set these in the environment to use a different server or collection.
BASE_URL = os.environ.get("CERES_URL", "http://localhost:8000").rstrip("/")
COLLECTION_ID = int(os.environ["CERES_COLLECTION_ID"])
DESTINATION = Path(f"collection_{COLLECTION_ID}")


def get_json(session, url, **kwargs):
    # Fail immediately on HTTP errors instead of saving an incomplete export.
    response = session.get(url, timeout=(5, 60), **kwargs)
    response.raise_for_status()
    return response.json()


def save_analysis(analysis):
    image_fields = ("photo", "segmented_photo")
    # Skip analyses without images, as described above.
    if not any((analysis.get(field) or {}).get("image") for field in image_fields):
        return False

    # Store each analysis in its own folder under the collection directory.
    directory = DESTINATION / str(analysis["id"])
    directory.mkdir(parents=True, exist_ok=True)
    # Keep results and details, replacing base64 payloads with file metadata.
    results = {
        key: value for key, value in analysis.items() if key not in image_fields
    }
    results["images"] = {}

    for field, filename in (
        ("photo", "original.jpg"),
        ("segmented_photo", "segmented.jpg"),
    ):
        photo = analysis.get(field)
        if not photo or not photo.get("image"):
            continue

        # Decode the API's base64 image and save it as a local JPEG file.
        content = base64.b64decode(photo["image"], validate=True)
        (directory / filename).write_bytes(content)
        results["images"][field] = {
            "file": filename,
            "mimetype": photo["mimetype"],
            "created": photo.get("created"),
        }

    (directory / "resultados.json").write_text(
        json.dumps(results, ensure_ascii=False, indent=2), encoding="utf-8",
    )
    return True


url = f"{BASE_URL}/api/v2/public/analyses/"
parameters = {
    "collection": COLLECTION_ID,
    "page": 1,
    "page_size": 50,
    "ordering": "id",
}
saved = 0
# Create this even if no analysis has an image to export.
DESTINATION.mkdir(parents=True, exist_ok=True)

with requests.Session() as session:
    # Follow each page; list rows omit images, so fetch every analysis detail.
    while url:
        page = get_json(session, url, params=parameters)
        for summary in page["results"]:
            analysis = get_json(
                session, f"{BASE_URL}/api/v2/public/analyses/{summary['id']}/",
            )
            if save_analysis(analysis):
                saved += 1

        url = page["next"]
        parameters = None  # The next URL already includes pagination parameters.

    # Collection detail includes the aggregate without repeating analysis records.
    aggregate = get_json(
        session,
        f"{BASE_URL}/api/v2/public/analysis-collections/{COLLECTION_ID}/",
        params={"include_analyses": "false"},
    )
    (DESTINATION / "resultados_agregados.json").write_text(
        json.dumps(aggregate, ensure_ascii=False, indent=2), encoding="utf-8",
    )

print(f"Saved {saved} analyses with images in {DESTINATION.resolve()}")
print(f"Saved collection aggregate in {DESTINATION / 'resultados_agregados.json'}")

Example output files (either image may be absent):

collection_10/
  resultados_agregados.json
  123/
    original.jpg
    segmented.jpg
    resultados.json

Limitations:

  • Both images are returned as JPEG recompressed at quality 30. The name original.jpg identifies the input photograph, not its original file bytes. The public API does not offer an uncompressed or original-file download.
  • Continuous analysis retains only representative images. photo and segmented_photo can be null without losing the analysis's numerical results. This script exports per-analysis results only for analyses with at least one image; the collection aggregate is saved separately in the main directory.
  • Downloading requires a detail request for every analysis, in addition to the paginated list requests. The public list does not include images or support has_image=true filtering.
  • Pagination does not provide a snapshot. For a completed continuous run, wait for status: stopped and pending_frames: 0, and avoid adding analyses to the collection during the download. Check failed_frames_count for processing failures; stopped does not guarantee every captured frame succeeded.
  • The script stops on HTTP, invalid base64 or filesystem errors and can leave partially written output. Running it again overwrites files for the same analysis IDs; it does not remove files left by previous runs.

Python example: asynchronous continuous run

Install requests. Set CERES_URL and CERES_CAMERA_ID to select a specific installed continuous camera, even when more than one exists. Choose one mode:

  • New collection: set CERES_COMMODITY_ID to a variety ID and optionally set CERES_TICKET. Leave CERES_COLLECTION_ID unset.
  • Existing collection: set CERES_COLLECTION_ID; its metadata will be reused. A previously created empty collection also uses this mode.

Save the following as continuous.py and run python continuous.py. It monitors capture for approximately ten seconds, unless the configured frame limit or another state change ends capture earlier, then requests stop if available. The server does the work asynchronously; this client polls REST every two seconds. It allows up to 120 seconds of polling for draining and reports a timeout without fetching a supposedly final aggregate. HTTP calls have their own timeouts. During draining, each poll reports the run status, successfully processed analyses, pending frames and failed frames. Pending frames include both queued and currently executing work; failures are counted separately from successes.

import json
import os
import time

import requests

BASE_URL = os.environ.get("CERES_URL", "http://localhost:8000").rstrip("/")
CAMERA_ID = int(os.environ["CERES_CAMERA_ID"])
# To reuse an existing collection, set its ID before running this script:
# export CERES_COLLECTION_ID=10
# To create a NEW collection automatically when starting the run:
# unset CERES_COLLECTION_ID
# export CERES_COMMODITY_ID=7  # Required variety ID for the new collection.
# An unset or empty CERES_COLLECTION_ID selects the new-collection mode.
COLLECTION_ID = os.environ.get("CERES_COLLECTION_ID")


def request(method, resource, **kwargs):
    response = requests.request(
        method, f"{BASE_URL}/api/v2/public/{resource}",
        timeout=(5, 30), **kwargs,
    )
    if not response.ok:
        raise RuntimeError(f"HTTP {response.status_code}: {response.text}")
    return response.json()


cameras = request("GET", "cameras/")
camera = next((item for item in cameras if item["id"] == CAMERA_ID), None)
if camera is None or camera["capture_mode"] != "continuous":
    raise ValueError("Select an installed camera configured for continuous capture.")

payload = {"camera_id": CAMERA_ID}
if COLLECTION_ID:
    payload["collection"] = int(COLLECTION_ID)
else:
    payload.update({
        "commodity": int(os.environ["CERES_COMMODITY_ID"]),
        "ticket": int(os.environ.get("CERES_TICKET", "12345")),
        "extra_data": {"batch": "LOT-2026-001"},
    })

# Do not automatically retry this POST if its response is lost.
run = request("POST", "continuous-runs/", json=payload)
print(f"Run ID: {run['id']}; collection ID: {run['collection']}", flush=True)
resource = f"continuous-runs/{run['id']}/"

capture_deadline = time.monotonic() + 10
while run["status"] in ("running", "degraded"):
    if time.monotonic() >= capture_deadline:
        break
    time.sleep(2)
    run = request("GET", resource)

# A configured frame limit may already have ended capture.
if "stop" in run["available_actions"]:
    run = request("POST", resource + "stop/")

drain_deadline = time.monotonic() + 120
while run["status"] != "stopped" or run["pending_frames"] != 0:
    print(
        f"Status: {run['status']}; processed: {run['analyses_processed']}; "
        f"pending: {run['pending_frames']}; failed: {run['failed_frames_count']}",
        flush=True,
    )
    if run["status"] in ("paused", "failed"):
        raise RuntimeError(
            f"Run needs attention: {run['status']}; actions: {run['available_actions']}"
        )
    if time.monotonic() >= drain_deadline:
        raise TimeoutError(f"Run {run['id']} is still processing. Keep its ID and check later.")
    time.sleep(2)
    run = request("GET", resource)

print(
    f"Status: {run['status']}; processed: {run['analyses_processed']}; "
    f"pending: {run['pending_frames']}; failed: {run['failed_frames_count']}",
    flush=True,
)
aggregate = request(
    "GET", f"analysis-collections/{run['collection']}/?include_analyses=false",
)
print(json.dumps(aggregate, indent=2))

analyses

analyses_list

Lista los análisis para terceros.

Authorizations:
None
query Parameters
page
integer

A page number within the paginated result set.

page_size
integer

Number of results to return per page.

Responses

Response samples

Content type
application/json
{
  • "count": 123,
  • "results": [
    ]
}

analyses_create

Recibe la request y saca la foto, analiza e inserta en la base de datos.

Authorizations:
None
Request Body schema:

Responses

Request samples

Content type
{
  • "calibres": {
    }
}

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    }
}

analyses_retrieve

ViewSet público con endpoints limitados a acciones expuestas.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis.

Responses

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    }
}

Update ticket and commodity

Update ticket, commodity (variety name) or extra_data. These fields belong to the collection; changes affect every linked analysis.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis.

Request Body schema:
ticket
integer

Ticket number associated with the analysis.

commodity
string

Name of the commercial variety

extra_data
any

Additional metadata associated with the analysis.

Responses

Request samples

Content type
{
  • "ticket": 0,
  • "commodity": "string",
  • "extra_data": null
}

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    },
  • "created": "2019-08-24T14:15:22Z",
  • "ticket": 0,
  • "id": 0,
  • "commodity": "string",
  • "extra_data": null,
  • "collection": 0,
  • "photo": {
    },
  • "segmented_photo": {
    }
}

Delete analysis

Removes the analysis identified by its ID. Returns HTTP 204 (No Content) on success.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis.

Responses

analyses_aggregate_retrieve

ViewSet público con endpoints limitados a acciones expuestas.

Authorizations:
None

Responses

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    }
}

analyses_create_no_frontend_create

This endpoint receives a request, creates a photo, and saves the analysis without frontend interaction.

Authorizations:
None
Request Body schema:
ticket
required
integer

Associated ticket number.

camera_id
integer or null

Optional camera identifier used when the request must capture a new photo.

commodity
required
string

The name of the existing commodity variety.

Responses

Request samples

Content type
{
  • "ticket": 0,
  • "camera_id": 0,
  • "commodity": "string"
}

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    }
}

analyses_list_no_frontend_retrieve

List the analyses in the system ordered by creation date.

Authorizations:
None

Responses

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    }
}

analysis-collections

List analysis collections

List analysis collections using standard DRF pagination. By default, the most recently modified collections are returned first.

Authorizations:
None
query Parameters
ordering
string

Comma-separated fields used to order the results. Prefix a field with '-' for descending order. Allowed fields: id, ticket, created, last_modified, commodity.

page
integer

Page number to retrieve.

page_size
integer

Number of collections per page.

search
string

A search term.

Responses

Response samples

Content type
application/json
{
  • "count": 123,
  • "results": [
    ]
}

Create analysis collection

Create a new analysis collection for the selected ticket and commodity.

Authorizations:
None
Request Body schema:
ticket
integer or null

Ticket number associated with the analysis.

commodity
required
integer

Integer ID of the commercial variety from GET /commodities/.

extra_data
any

Additional metadata associated with the analysis collection.

Responses

Request samples

Content type
{
  • "ticket": 0,
  • "commodity": 0,
  • "extra_data": null
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "ticket": 0,
  • "commodity": {
    },
  • "extra_data": null,
  • "created": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z",
  • "analyses_count": 0,
  • "category": "string",
  • "ISC": 0,
  • "IRC": 0,
  • "latest_continuous_run": { },
  • "calibres": {
    },
  • "dataset": [
    ],
  • "analyses": [
    ]
}

Retrieve analysis collection

Return a collection with its aggregated data and linked analyses.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis collection.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "ticket": 0,
  • "commodity": {
    },
  • "extra_data": null,
  • "created": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z",
  • "analyses_count": 0,
  • "category": "string",
  • "ISC": 0,
  • "IRC": 0,
  • "latest_continuous_run": { },
  • "calibres": {
    },
  • "dataset": [
    ],
  • "analyses": [
    ]
}

Update analysis collection

Update the ticket, commodity, or extra data of an existing collection.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis collection.

Request Body schema:
ticket
integer or null

Ticket number associated with the analysis.

commodity
integer

Integer ID of the commercial variety from GET /commodities/.

extra_data
any

Additional metadata associated with the analysis collection.

Responses

Request samples

Content type
{
  • "ticket": 0,
  • "commodity": 0,
  • "extra_data": null
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "ticket": 0,
  • "commodity": {
    },
  • "extra_data": null,
  • "created": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z",
  • "analyses_count": 0,
  • "category": "string",
  • "ISC": 0,
  • "IRC": 0,
  • "latest_continuous_run": { },
  • "calibres": {
    },
  • "dataset": [
    ],
  • "analyses": [
    ]
}

Delete collection

Removes the collection and all analyses linked to it.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis collection.

Responses

Collection analyses

List analyses inside the collection using standard DRF pagination.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis collection.

query Parameters
ordering
string

Comma-separated fields used to order the results. Prefix a field with '-' for descending order. Allowed fields: id, created, last_modified, indice_sanitario_ceresia, indice_recogida_ceresia, category.

page
integer

Page number to retrieve.

page_size
integer

Number of analyses per page.

search
string

A search term.

Responses

Response samples

Content type
application/json
{
  • "count": 123,
  • "results": [
    ]
}

Create analysis in collection

Capture and analyze a photo using camera_id, inheriting ticket, variety and metadata from this collection. Returns synchronously.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this analysis collection.

Request Body schema:
photo
integer

The photograph under analysis, returned compressed at 30% quality.

camera_id
integer or null

Optional camera identifier used when the request must capture a new photo.

Responses

Request samples

Content type
{
  • "photo": 0,
  • "camera_id": 0
}

Response samples

Content type
application/json
{
  • "category": "string",
  • "dataset": [
    ],
  • "ISC": 100,
  • "IRC": 100,
  • "calibres": {
    },
  • "created": "2019-08-24T14:15:22Z",
  • "ticket": 0,
  • "id": 0,
  • "commodity": "string",
  • "extra_data": null,
  • "collection": 0,
  • "photo": {
    },
  • "segmented_photo": {
    }
}

cameras

List cameras

List installed cameras and their capture modes. Use the camera ID as camera_id when creating analyses or continuous runs.

Authorizations:
None

Responses

Response samples

Content type
application/json
[
  • {
    }
]

commodities

Varieties of the fruit

This endpoint shows all fruit varieties that can be used in analyses.

Authorizations:
None

Responses

Response samples

Content type
application/json
[
  • {
    }
]

Create variety

Creates a new fruit variety.

Authorizations:
None
Request Body schema:
name
required
string <= 200 characters

Name of the commercial variety

display_name
string <= 200 characters

Human-readable label shown in the UI. If left empty, it defaults to the value of 'name'.

Responses

Request samples

Content type
{
  • "name": "string",
  • "display_name": "string"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "display_name": "string"
}

Retrieve variety

Retrieve the details of a variety by its ID.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this commodity.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "display_name": "string"
}

Update variety

Completely replaces an existing variety.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this commodity.

Request Body schema:
name
required
string <= 200 characters

Name of the commercial variety

display_name
string <= 200 characters

Human-readable label shown in the UI. If left empty, it defaults to the value of 'name'.

Responses

Request samples

Content type
{
  • "name": "NewVariety",
  • "display_name": "New Variety"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "display_name": "string"
}

Partially update variety

Performs a partial update of specific variety fields.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this commodity.

Request Body schema:
name
string <= 200 characters

Name of the commercial variety

display_name
string <= 200 characters

Human-readable label shown in the UI. If left empty, it defaults to the value of 'name'.

Responses

Request samples

Content type
{
  • "name": "UpdatedVariety",
  • "display_name": "New Variety"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "name": "string",
  • "display_name": "string"
}

Delete variety

Deletes a variety by its ID.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this commodity.

Responses

continuous-runs

Start public continuous analysis

Start asynchronous capture and processing with an explicit camera_id when multiple cameras are installed. Choose a camera whose capture_mode is continuous, using GET /api/v2/public/cameras/. HTTP 201 returns a run ID and collection ID; it does not mean results are ready.

New collection: omit collection and send commodity as a variety ID, with optional ticket and extra_data. A collection is created automatically.

Existing collection: send collection as its ID and camera_id. The existing ticket, variety and metadata are reused; omit commodity, ticket and extra_data. New analyses are added to previous results.

Capture stops automatically at the installation's continuous_analysis_limit (default 300 captured frames per run, not seconds or successful analyses). This limit is configured by an administrator, not in this request. Resume does not reset the frame count. To stop earlier, POST /api/v2/public/continuous-runs/{id}/stop/.

Poll GET /api/v2/public/continuous-runs/{id}/ every two seconds until status is stopped and pending_frames is zero, including after an automatic stop. A stop response can still be stopping or draining. Check failed_frames_count: finished frames may have failed. Use available_actions if capture is paused or failed.

Then GET /api/v2/public/analysis-collections/{collection}/?include_analyses=false for the collection aggregate, including earlier analyses if reused. GET /api/v2/public/analyses/aggregate/?ticket={ticket} instead aggregates all collections for that ticket. Active-run results are partial. See the integration guide's asynchronous continuous-run Python example.

Authorizations:
None
Request Body schema:
collection
integer or null
ticket
integer or null
commodity
integer
extra_data
any
camera_id
integer or null

Optional camera identifier used to start the public continuous run.

Responses

Request samples

Content type
Example
{
  • "camera_id": 2,
  • "commodity": 7,
  • "ticket": 12345,
  • "extra_data": {
    }
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Retrieve continuous run

Read capture and processing progress. Poll every two seconds and use available_actions. A stop request is complete only when status is stopped and pending_frames is zero.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this continuous analysis run.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Cancel queued public processing after capture has stopped

Keep legacy/private responses intact while improving public v2.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this continuous analysis run.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Pause public continuous analysis

Pause frame capture while preserving the run and collection.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this continuous analysis run.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Resume public continuous analysis

Resume a paused or failed run using the same collection.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this continuous analysis run.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

Stop public continuous analysis

Request capture to stop. The response may still be stopping or draining; poll until stopped with zero pending_frames. Failed frames remain counted.

Authorizations:
None
path Parameters
id
required
integer

A unique integer value identifying this continuous analysis run.

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "collection": 0,
  • "camera": {
    },
  • "status": "running",
  • "frames_captured": 2147483647,
  • "frames_processing": 0,
  • "analyses_processed": 2147483647,
  • "failed_frames_count": 2147483647,
  • "cancelled_frames_count": 0,
  • "processing_cancelled_at": "2019-08-24T14:15:22Z",
  • "consecutive_failures": 2147483647,
  • "pending_frames": 0,
  • "error_message": "string",
  • "available_actions": [
    ],
  • "created": "2019-08-24T14:15:22Z",
  • "started_at": "2019-08-24T14:15:22Z",
  • "stopped_at": "2019-08-24T14:15:22Z",
  • "last_modified": "2019-08-24T14:15:22Z"
}

features

Features of the photo

This endpoint shows the feature types that can appear in an analysis.

Authorizations:
None

Responses

Response samples

Content type
application/json
[
  • {
    }
]