Client Lambda API

The browser calls the lambda-resize HTTP API for operations that require video or frame access. Flask owns HTML and metadata APIs; lambda-resize owns first frame extraction, playback URL generation, and retracing.

See ArchitectureDesign.md for the current service boundary.

Lambda API Base URL

Flask injects the Lambda API base URL into pages as the browser global LAMBDA_API_BASE.

  • Template: src/app/templates/base.html

  • Server: src/app/apikey.py, via get_lambda_api_base()

  • Local override: PLANTTRACER_LAMBDA_API_BASE=http://127.0.0.1:9811/

  • Deployed same-origin stack: https://{stack}.planttracer.com/

  • Fallback when no explicit base is configured: https://{HOSTNAME}.{DOMAIN}/, or the current request origin when those variables are absent.

All client calls are authorized. The browser sends the current api_key; the Lambda validates it against DynamoDB.

Static JavaScript and CSS are not part of LAMBDA_API_BASE; they are served same-origin by Flask/lambda-web under /static/* until there is a versioned asset plan for external static hosting.

Endpoints

Operation

Method

Path

Auth

Purpose

Ping

GET

/resize-api/v1/ping

none

Health check; returns { "error": false, "status": "ok", ... } with app_version, deployed_at, and selected stack parameters.

Complete upload (local adapter)

POST

/resize-api/v1/process-upload

x-api-key header

Completes a MinIO staging upload through the same service used by the AWS EventBridge handler.

Finish camera capture

POST

/resize-api/v1/finish-camera

x-api-key header

After STOP and all frame uploads finish, assembles ordered JPEG frames into the durable source MP4 and starts normal movie processing.

First frame

GET

/resize-api/v1/first-frame?api_key=...&movie_id=...

query api_key

Returns JPEG frame 0 with saved rotation applied and scaled to the analysis size.

Movie data

GET

/resize-api/v1/movie-data?api_key=...&movie_id=...&format=json

query api_key

Returns signed playback/download URLs as JSON.

Movie data redirect

GET

/resize-api/v1/movie-data?api_key=...&movie_id=...

query api_key

302 redirect to signed movie URL.

Movie zip redirect

GET

/resize-api/v1/movie-data?api_key=...&movie_id=...&format=zip

query api_key

302 redirect to signed frame ZIP URL if present.

Trace movie

POST

/resize-api/v1/trace-movie

x-api-key header

Queues retracing from a user-edited source frame through an optional end frame.

Complete Upload Request

POST /resize-api/v1/process-upload

x-api-key: <api_key>
Content-Type: application/json
{ "movie_id": "m..." }

This is a local-development compatibility adapter for MinIO. Deployed browsers do not call it; S3 Object Created events reach lambda-resize through EventBridge. The caller must be allowed to edit the movie. Lambda-resize reads the staging object with HeadObject, rejects a missing object or a byte count different from upload_bytes_expected, copies it to the durable key, records upload metadata, deletes staging, and starts post-upload processing. Read-only superauditors cannot complete or otherwise mutate movies; superadmins can.

The EventBridge invocation is not a public HTTP endpoint. Its Pydantic envelope validation additionally checks the event source/type, bucket, deployment prefix, course/movie identifiers, and corresponding DynamoDB row.

Finish Camera Capture Request

POST /resize-api/v1/finish-camera

x-api-key: <api_key>
Content-Type: application/json
{ "movie_id": "m..." }

The caller must have edit access to a camera movie still in uploading state. The service verifies that uploaded frame numbers are contiguous from zero, claims the movie’s processing lease, and queues camera assembly. A concurrent STOP that loses the lease claim returns HTTP 409. The worker encodes the frames into the durable source MOV/MP4 object, writes capture and research attribution metadata, records frame and byte counts, and invokes normal post-upload processing. After processing succeeds, it removes the temporary camera JPEGs; retries repeat this cleanup safely. Invalid credentials or missing movie access return HTTP 403, while missing frames or a movie that is not accepting STOP return HTTP 409. Local development processes immediately unless local async queue mode is enabled; deployed stacks publish the job to EventBridge.

Trace Movie Request

POST /resize-api/v1/trace-movie

Headers:

x-api-key: <api_key>
Content-Type: application/json

Body:

{ "movie_id": "m...", "frame_start": 12, "frame_end": 200 }

frame_start is the edited source frame. Analyze saves its visible markers before queuing. Plant Tracer requires those markers, preserves that frame, clears stored trackpoints after it through frame_end when supplied, marks the movie as tracing, and dispatches work. In local mode the work goes to the in-process queue; in deployed mode a stack-scoped EventBridge rule pushes the custom work event to lambda-resize without idle polling. Rejected source frames return HTTP 403 with JSON error and message fields that Analyze displays to the user. If the source becomes empty after the request consumes its Analyze lease, the response also includes lease_reacquire_required: true; Analyze becomes read-only until reopened. If publishing the queued work fails, the API releases a lease that has not yet been claimed and returns HTTP 503 with the same reacquire flag. A worker that already claimed the job retains its lease. An HTTP 409 for a request carrying an Analyze lease also includes the flag; this covers retries whose first response was lost after consuming that lease. Failures before trace-lease acquisition return HTTP 503 without that flag; Analyze keeps its current editing lease. After tracing completes, Analyze reacquires an editing lease before enabling marker edits or Reset Trace again.

Local Development

Use the Makefile instead of hand-built commands:

make run-local-lambda-debug
make run-local-debug

run-local-lambda-debug starts a Flask bridge that converts local HTTP requests into API Gateway v2 events and calls resize_app.main.lambda_handler().