19 Images
23 operations.
19.0.1 listImages
GET /api/images
Searches images across datasets by tag, free text, thermal type or a geographic radius — the general-purpose image query behind search and the AI tools.
Why it exists. ‡ Search images across datasets by tag, free text, thermal type or a geographic radius. The general-purpose image query behind search and the AI tools, as opposed to the dataset-scoped gallery listing.
Called by — lib/workspaces/queries/candidates.ts
Reached from — /progress · /w/data-explorer/anomalies · /w/integrity/campaigns · /w/integrity/evidence-explorer · /w/integrity/insights · /w/integrity/investigate
Search/filter/paginate images with dataset, tag, filename, and thermal-type filters, optionally inlining base64 thumbnails; results are RLS-scoped to the caller’s datasets. lat/lng/radius parameters are accepted but not applied.
Authentication: CookieAuth, SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — not readable from the source: the handler returns a value assembled at run time rather than a literal.
Effect — reads datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
dataset_id |
query | no | string (uuid) | |
dataset_slug |
query | no | string | |
thermal_type |
query | no | string — one of thermal, rgb |
|
tags |
query | no | string | |
search |
query | no | string | |
limit |
query | no | integer | |
offset |
query | no | integer | |
base64 |
query | no | string — one of true |
|
lat |
query | no | number | |
lng |
query | no | number | |
radius |
query | no | number |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Matching images, paginated. | |
400 |
Malformed lat/lng/radius. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
503 |
Geographic search is not available on this deployment. | Error |
19.0.2 resolveImageReferences
POST /api/images
Fetches up to fifty images by id in one request, so a chat answer that references several images can resolve them all at once.
Why it exists. ‡ Fetch up to fifty images by id in one request, so a chat answer that references several images can resolve them all at once instead of issuing a request per image.
Called by — app/api/test-image-visualization/route.ts · modules/gallery/services/resolve-image-refs.ts
Batch-fetches metadata for up to 50 images by id — filename, thumbnail_url, dataset_name, dataset_slug — optionally attaching base64 payloads (RLS-scoped — images outside the caller’s datasets are silently omitted); ready_for_ai counts images whose base64 fetch succeeded. Also the metadata resolver for MARKDOWN_REPORT image references (AG-UI contract §7.4.1 form D), which uses the per-image dataset_slug for click-to-viewer URLs. It is a frontend resolver, not a skill input: preparing media for an image skill is POST /api/media/resolve (ADR-006), which carries no slug, path, or URL. The ready_for_ai counter reports base64 fetch success and does not mean an analysis ran.
Authentication: SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — batch_id · images · analysis_type · total_processed · ready_for_ai · message
Field names as the handler returns them; the source states no types for them.
Effect — reads images
Request body
| Field | Type | Required |
|---|---|---|
image_ids |
array of string | yes |
analysis_type |
string | no |
batch_request |
boolean | no |
include_base64 |
boolean | no |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Resolved images. | |
400 |
image_ids array is required, and at most 50 entries. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
19.0.3 listImageCandidates
GET /api/images/candidates
Lists a campaign’s review candidates — tagged images awaiting engineering attention — with their review state and asset attribution.
Scans up to limit images in a dataset (default 200) and returns every one carrying at least one tag, each with its priority (crit/mod/low, from its own tags), review state (new/confirmed/dismissed, from decided equipment_damage_mechanisms citing it), and asset attribution (nearest active image_asset_link, explicit only — never image_asset_proximity geo-guessing). Backed by public.v_image_review_candidates so this and Kawa’s equivalent tool compute the same answer from the same definition. scanned is the total images considered (tagged and untagged), matching kap_list_candidates’ “N of M images scanned” framing.
Authentication: CookieAuth, SupabaseAuth
Returns — datasetSlug · scanned · candidates
Field names as the handler returns them; the source states no types for them.
Effect — reads v_image_review_candidates
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
dataset_slug |
query | yes | string | |
limit |
query | no | integer |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Review candidates for the dataset, plus the total images scanned. | |
400 |
dataset_slug is required. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
19.0.4 getImageExif
GET /api/images/exif
Reads the EXIF block out of one stored image — camera, GPS, timestamp — for inspecting what the camera actually recorded.
Why it exists. † Read the EXIF block out of one stored image and return it, for inspecting what the camera actually recorded when a position or timestamp looks wrong.
Extracts EXIF metadata for a given image by ID (RLS-scoped — an image outside the caller’s datasets 404s). Not an agent tool yet. The response echoes the stored filename, which the UUID-only boundary excludes: filenames in this domain encode capture structure (_T. / _V., mission and path fragments), and a model handed one starts reasoning about it and repeating it in chat. Promoting this operation needs the field dropped from the handler first — a behaviour change, not a spec edit.
Authentication: CookieAuth, SupabaseAuth
Returns — success · imageId · filename · exif
Field names as the handler returns them; the source states no types for them.
Effect — reads datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
imageId |
query | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
EXIF data (possibly empty if none was found). | |
400 |
Missing imageId, invalid storage path, or unsupported provider. | Error |
401 |
Not authenticated. | Error |
404 |
Image or dataset not found. | Error |
500 |
Server error. | Error |
501 |
Not implemented for SharePoint-backed datasets. | Error |
19.0.5 extractExifFromUpload
POST /api/images/exif
Reads the EXIF block out of image bytes posted directly, so a file can be inspected before it is ingested into a dataset.
Why it exists. † Read the EXIF block out of image bytes posted directly, so a file can be inspected before it is ingested into a dataset.
Extracts EXIF metadata from a binary image upload (buffer in body).
Authentication: CookieAuth, SupabaseAuth
Returns — success · exif
Field names as the handler returns them; the source states no types for them.
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
EXIF data (possibly empty if none was found). | |
400 |
Missing or empty request body. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
19.0.6 extractImageMetadata
GET /api/images/extract-metadata
Extracts and stores one image’s capture metadata — position, camera, timestamp — from the image itself.
Why it exists. ‡ Extract and store one image’s capture metadata — position, camera, timestamp — from the image itself, for repairing a record whose metadata is missing or wrong.
Called by — modules/gallery/overlays/components/image-viewer.tsx · modules/gallery/overlays/components/pairs-viewer.tsx
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
Extracts and stores metadata for a given image by ID. Access is checked under RLS (404 when out of scope); the metadata write is service-role.
Authentication: CookieAuth, SupabaseAuth
Returns — success · image
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
imageId |
query | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Metadata extracted and stored. | |
400 |
Missing imageId, invalid storage path, or unsupported provider. | Error |
401 |
Not authenticated. | Error |
404 |
Image or dataset not found. | Error |
500 |
Server error. | Error |
19.0.7 extractDatasetImageMetadata
POST /api/images/extract-metadata
Extracts capture metadata for a batch of a dataset’s images in one pass, to backfill a dataset indexed without EXIF.
Why it exists. ‡ Extract capture metadata for a batch of a dataset’s images in one pass, so a dataset indexed without EXIF can be backfilled without a request per image.
Called by — app/(authenticated)/datasets/[slug]/components/extract-metadata-button.tsx
Reached from — /datasets/<slug>
Extracts and stores metadata for all images in a dataset, optionally limited. Access is checked under RLS (404 when out of scope); the metadata writes are service-role.
Authentication: CookieAuth, SupabaseAuth
Returns — success · message · processed · totalImages · successCount · failedCount · foundXMPCount · totalImagesInBatch
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) datasets · images
Request body
| Field | Type | Required |
|---|---|---|
datasetId |
string | yes |
limit |
integer | no |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Processed (possibly zero images, if none needed it). | |
400 |
Missing datasetId. | Error |
401 |
Not authenticated. | Error |
404 |
Dataset not found. | Error |
500 |
Server error. | Error |
19.0.8 searchDatasetImages
GET /api/images/search
Searches a dataset’s images by filename, tag or description, with pagination and sorting, optionally restricted to GPS-tagged images.
Why it exists. ‡ Search a dataset’s images by filename or description with pagination and sorting, optionally restricted to images that carry GPS. The gallery’s search box.
Called by — components/image-search.tsx · modules/gallery/core/hooks/use-gallery-search-controller.ts · modules/gallery/ui/components/image-gallery-renderer.tsx
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
Searches images in a dataset by tags or metadata; RLS-scoped, so an inaccessible dataset behaves like a missing one (404 via slug, empty via id).
Authentication: CookieAuth, SupabaseAuth
Returns — success · images · totalImages · currentPage · totalPages · pageSize · filterOptions · searchQuery · searchStats
Field names as the handler returns them; the source states no types for them.
Effect — reads datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
datasetId |
query | no | string | |
datasetSlug |
query | no | string | |
query |
query | no | string | |
exact |
query | no | string — one of true, false |
|
gpsOnly |
query | no | string — one of true, false |
|
sort |
query | no | string — one of filename_asc, filename_desc |
|
page |
query | no | integer | |
pageSize |
query | no | integer |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Matching images, paginated. | |
400 |
Missing datasetId or datasetSlug. | Error |
401 |
Not authenticated. | Error |
404 |
Dataset not found for the given slug. | Error |
500 |
Server error. | Error |
19.0.9 listImageSuggestions
GET /api/images/suggestions
Suggests tag completions from the tags actually present in a dataset, so the search box offers terms that will return results.
Why it exists. ‡ Suggest tag completions from the tags actually present in a dataset, so the search box offers terms that will return results rather than a fixed vocabulary.
Called by — modules/gallery/core/hooks/use-gallery-search-controller.ts
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
Suggests tags or metadata values for images in a dataset based on a query string; RLS-scoped, so an inaccessible dataset yields empty suggestions.
Authentication: CookieAuth, SupabaseAuth
Returns — success · suggestions
Field names as the handler returns them; the source states no types for them.
Effect — reads annotation_categories · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
datasetId |
query | yes | string | |
query |
query | no | string | |
limit |
query | no | integer | |
all |
query | no | string — one of true, false |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Suggested completions. | |
400 |
Missing datasetId. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
19.0.10 listDatasetThumbnails
GET /api/images/thumbnails
Returns a dataset’s thumbnails for preview strips.
Why it exists. † Return a dataset’s thumbnails for preview strips. Accepts either a browser session or a bearer token, having been built for tool callers as well as the interface.
Thumbnail list for a dataset identified by slug, with optional thermal (_T.) / rgb (_V.) filename filtering. Accepts a bearer token (forwarded to Supabase, RLS applies) or the session cookie.
Authentication: SupabaseAuth, CookieAuth
Returns — images · dataset · count
Field names as the handler returns them; the source states no types for them.
Effect — reads datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
dataset_slug |
query | yes | string | |
limit |
query | no | integer | |
type |
query | no | string — one of thermal, rgb |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Thumbnails. | |
400 |
dataset_slug is required. | Error |
401 |
Not authenticated. | Error |
404 |
Dataset not found. | Error |
19.0.11 getImage
GET /api/images/{id}
Fetches one image’s stored record — metadata, tags, and any analysis attached to it — optionally with the bytes inlined.
Why it exists. ‡ Fetch one image’s stored record — its metadata, tags and any analysis attached to it — optionally with the image bytes inlined for a caller that cannot follow a storage URL.
Called by — app/(authenticated)/w/integrity/investigate/investigate-client.tsx · components/main-panel-renderer.tsx · components/workspaces/widgets/image-viewer-dialog.tsx · lib/mcp/manifest.ts …and 2 more
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/data-explorer/categories · /w/data-explorer/new-dataset · /w/data-explorer/storage …and 9 more
Single image metadata (RLS-scoped — an image outside the caller’s datasets 404s), optionally with base64 fields (base64=true); format=base64 (combined with base64=true) returns the raw base64 body with the image’s content type instead of JSON.
Authentication: CookieAuth, SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — not readable from the source: the handler returns a value assembled at run time rather than a literal.
Effect — reads images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Image details. | |
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.12 prepareImageForAnalysis
POST /api/images/{id}
Deprecated. A placeholder for single-photo AI analysis that never performed any — it echoes the photo back. Preparing a photo for analysis is POST /media/resolve; the analysis itself runs in the AI backend.
Why it exists. † Return one image packaged for AI analysis. A placeholder: it prepares the payload and says so rather than performing an analysis, which has not been wired up.
Deprecated. Stub analysis entry point — re-fetches its own GET with base64 (forwarding the caller’s credentials) and echoes the payload marked ready_for_ai. No analysis is performed, and none ever was. It predates ADR-006 and claims a job two other surfaces now do properly: POST /api/media/resolve prepares media for a skill under the UUID-only boundary, and image analysis itself runs through the gateway’s skill-execution endpoint (ADR-007). Kept only until its callers are confirmed gone; a new caller should use neither this nor a base64 echo.
Authentication: CookieAuth, SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — not readable from the source: the handler returns a value assembled at run time rather than a literal.
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
analysis_type |
string | no |
request_base64 |
boolean | no |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Packaged. | |
400 |
Base64 image data required for AI processing. | Error |
401 |
Not authenticated. | Error |
500 |
Server error. | Error |
19.0.13 analyzeImage
POST /api/images/{id}/analyze
Dispatches one image to the AI gateway’s analysis skill and records the attempt with its suggestions.
Why it exists. ‡ Analyse one photograph on request from the viewer, so an engineer can ask for a reading directly instead of hoping an assistant chooses to look.
Called by — modules/gallery/core/hooks/use-batch-analysis.ts · modules/gallery/core/hooks/use-suggestion-review.ts
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
Runs an image skill over a single image and records what it found. This route dispatches; it does not analyze. The skill executes in the AI gateway (ADR-007), which resolves the image to prepared bytes as the caller (ADR-006) and returns the canonical ImageAnalysisResult. The caller’s own JWT is what travels — never a platform credential, or media resolution would authorize the platform instead of the person. The image is authorized under the caller’s scope before anything is dispatched, so an image outside their organization is a 404 and costs no model invocation. The caller’s tier is checked before dispatch too. Recording an attempt is editor-tier work, so a viewer’s analysis would otherwise run, cost a model invocation, and be discarded at the point of storage — reported as a failure to record, which is not what happened. A viewer gets 403: they can see the image, so a 404 would only mislead. The attempt and its suggestions are written in one transaction. A gateway refusal records nothing at all: an attempt row marks an image analyzed, and the “already analyzed” test reads attempts. Findings without geometry are not stored as suggestions — a reviewer cannot draw or accept a box that has no region.
Authentication: SupabaseAuth
Returns — attempt · suggestions
Field names as the handler returns them; the source states no types for them.
Effect — reads annotation_suggestions · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
skill_id |
string | no |
skill_version |
string | no |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Analysis attempt recorded. | |
401 |
Not authenticated, or no access token available to authorize media resolution. | Error |
403 |
Analysis requires member or admin access to this dataset. | Error |
404 |
Image not found, or the analysis skill is unknown. | Error |
500 |
Analysis succeeded but could not be recorded. | Error |
502 |
Analysis service rejected the request, or returned no result. | Error |
503 |
Analysis service unreachable. | Error |
19.0.14 addImageAssetTag
POST /api/images/{id}/asset-tag
Confirms that a photograph shows a particular piece of equipment, promoting an OCR-read suggestion to a confirmed tag.
Why it exists. ‡ Confirm that a photograph shows a particular piece of equipment, moving it from ‘taken nearby’ to evidence about that asset.
Confirms (approves) that an image shows an equipment asset, promoting an OCR-read suggestion to a confirmed tag. The tag is added to the image’s tags set (the confirmed store), and the approval — who confirmed it and when — is appended to metadata.tag_approvals for the audit trail. The call is idempotent: a tag already confirmed returns 200 with the current tags and adds no duplicate. The tag must name a real asset in the register; an arbitrary string is refused (400) so only known equipment can enter tags. Authorized by the database, not by this handler (CONTRACT-API-001): the editor-scoped RLS policy on public.images (“Users can update images in their datasets”, via is_org_editor_secure) governs the write. An image outside the caller’s organizations is a 404, indistinguishable from one that does not exist; a caller who can read the image but is not an editor gets a 403 that names the role required. Pair propagation: a visual/thermal pair shares asset tags. When the image is a _V/_T member, the same confirmation is applied to its counterpart, identified by filename convention (not data_group_id, which is unpopulated). The counterpart id is returned as paired_image_id (null when there is none). Propagation is best-effort and RLS-scoped: a counterpart write RLS declines is silently skipped and never changes this response.
Authentication: CookieAuth, SupabaseAuth
Returns — image_id · tag · tags · paired_image_id
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) equipment · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
tag |
string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Confirmed (idempotent — an already-confirmed tag returns the current state). | |
400 |
tag is required, or names no known asset. | Error |
401 |
Not authenticated. | Error |
403 |
You have read access to this image but not permission to edit it. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.15 removeImageAssetTag
DELETE /api/images/{id}/asset-tag
Withdraws a confirmed asset tag, for a photograph attributed to the wrong asset.
Why it exists. ‡ Withdraw that confirmation, for a photograph attributed to the wrong asset.
Removes (rejects) an asset tag from an image — the “reject” half of the suggest-then-approve model, the counterpart to the POST. The tag is dropped from the image’s tags set (the confirmed store), and the rejection — who rejected it and when — is appended to metadata.tag_rejections for the audit trail. A recorded rejection also suppresses the suggestion: getEquipment will not re-offer a tag this image was rejected as. The call is idempotent: rejecting a tag the image never carried still records the rejection and returns 200. Authorized by the database, not by this handler (CONTRACT-API-001): the editor-scoped RLS policy on public.images governs the write. An image outside the caller’s organizations is a 404; a caller who can read the image but is not an editor gets a 403 that names the role required. Pair propagation: a visual/thermal pair shares asset tags. When the image is a _V/_T member, the same rejection is applied to its counterpart, identified by filename convention (not data_group_id, which is unpopulated). The counterpart id is returned as paired_image_id (null when there is none). Propagation is best-effort and RLS-scoped: a counterpart write RLS declines is silently skipped and never changes this response.
Authentication: CookieAuth, SupabaseAuth
Returns — image_id · tag · tags · paired_image_id
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
tag |
string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Rejected (idempotent — rejecting a tag the image never carried still records the rejection). | |
400 |
tag is required. | Error |
401 |
Not authenticated. | Error |
403 |
You have read access to this image but not permission to edit it. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.16 updateImageMetadata
PATCH /api/images/{id}/metadata
Shallow-merges a partial object into one image’s stored metadata, without resupplying the whole object.
Why it exists. † Amend one image’s stored metadata without resupplying the whole object, so a correction to a single field cannot silently discard the rest of the record.
Called by — scripts/phash-backfill.ts
Shallow-merges a partial object into an image’s metadata (jsonb). The merge is top-level only: a key in the patch replaces that key wholesale, and any top-level key absent from the patch is preserved. Authorized by the database, not by this handler (CONTRACT-API-001): the editor-scoped RLS policy on public.images (“Users can update images in their datasets”, via is_org_editor_secure) governs the write. An image outside the caller’s organizations is a 404, indistinguishable from one that does not exist; a caller who can read the image but is not an editor gets a 403 that names the role required.
Authentication: CookieAuth, SupabaseAuth
Returns — image
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
metadata |
object | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Updated. | |
400 |
metadata must be a JSON object. | Error |
401 |
Not authenticated. | Error |
403 |
You have read access to this image but not permission to edit it. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.17 getImageOcr
GET /api/images/{id}/ocr
Returns the OCR text a prior run stored for one image — a cheap read, no model invoked.
Why it exists. † Return what was previously read from a photograph’s placard, so a proposal can be reviewed without running the read again.
Returns the OCR text a prior run stored in the image’s metadata (images.metadata.ocr). A cheap read — no model is invoked and the image bytes are not downloaded. RLS-scoped — an image outside the caller’s datasets 404s. ocr is null when the image was never OCR’d; an ocr object with an empty texts array means it was OCR’d and no text was found.
Authentication: CookieAuth, SupabaseAuth
Returns — image_id · ocr
Field names as the handler returns them; the source states no types for them.
Effect — reads images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
ocr is null when the image was never OCR’d. |
|
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.18 extractImageText
POST /api/images/{id}/ocr
Dispatches one image to the AI gateway’s text-reading skill and persists the result onto the image’s metadata.
Why it exists. † Read the equipment ID placard in a photograph, so the platform can propose which asset the frame shows rather than leaving an engineer to identify it by eye.
Runs the open-vocabulary image.read-text skill over a single image and stores what it read. Like analyzeImage this route dispatches; it does not read text itself. The skill executes in the AI gateway (ADR-007), which resolves the image to prepared bytes as the caller (ADR-006). The caller’s own JWT is what travels — never a platform credential, or media resolution would authorize the platform instead of the person. The image is authorized under the caller’s scope before anything is dispatched, so an image outside their organization is a 404 and costs no model invocation. The caller’s tier is checked before dispatch too: running OCR and persisting its result is editor-tier work, so a viewer gets 403 (they can see the image, so a 404 would only mislead). Every stored read carries text, kind, confidence and region: the skill reads verbatim and returns a normalised box, and the route classifies each string as asset_tag (a plant equipment tag by the tag grammar) or text, because the image→asset link writers select on kind = 'asset_tag'. The read result is persisted onto images.metadata.ocr with a shallow top-level merge — the same merge updateImageMetadata performs — so the read is durable and the sibling GET /images/{id}/ocr reflects it.
Authentication: CookieAuth, SupabaseAuth
Returns — image_id · status · ocr
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Text extracted and stored. | |
401 |
Not authenticated, or no access token available to authorize media resolution. | Error |
403 |
OCR requires member or admin access to this dataset. | Error |
404 |
Image not found, or the OCR skill is unknown. | Error |
500 |
OCR ran but its result could not be stored. | Error |
502 |
OCR service rejected the request, or returned no result. | Error |
503 |
OCR service unreachable. | Error |
19.0.19 getImagePair
GET /api/images/{id}/pair
Finds the visual or thermal counterpart of one image, so the two frames of a capture can be viewed together.
Why it exists. ‡ Find the visual or thermal counterpart of one image, so the two frames of a capture can be viewed together instead of being hunted for by filename.
Called by — modules/gallery/overlays/components/image-viewer.tsx
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
The visual/thermal counterpart of one image, so the viewer can offer a side-by-side or overlay of the pair. A field capture produces two frames of the same scene — a visual (RGB) and a thermal — paired by filename convention only (not data_group_id, which is unpopulated). RLS-scoped: an image outside the caller’s datasets is a 404, and a counterpart the caller cannot see is simply absent. When there is no counterpart the response is { pair: null }; otherwise pair carries the counterpart’s id, filename, thumbnail URL, dataset slug (pairs are same-dataset), and kind — ‘rgb’ or ‘thermal’.
Authentication: CookieAuth, SupabaseAuth
Returns — pair
Field names as the handler returns them; the source states no types for them.
Effect — reads images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
The counterpart, or { pair: null } when there is none. |
|
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.20 listImageAnnotationSuggestions
GET /api/images/{id}/suggestions
Returns the machine’s proposed marks for one photograph, so a reviewer can see what was suggested without having been the one who asked.
Why it exists. ‡ Return the machine’s proposed marks for one photograph, so a reviewer can open the queue later and see what was suggested rather than only in the tab that asked for it.
Called by — modules/gallery/core/hooks/use-suggestion-review.ts
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
What has been proposed for this image and where each proposal stands (WP-3). analyzeImage returns the suggestions it just produced, but only to the caller that asked; a reviewer who reloads, or who arrives at an image someone else analyzed, reads them back from here. Each suggestion carries its latest decision, which is a projection. The append-only log is the record; this operation answers “where does this stand now?”, not “what happened to it”. The attempts are here too, including the failed ones: an image that could not be analyzed otherwise looks exactly like an image with nothing wrong in it. The image is authorized under the caller’s own scope first, so one outside their organization 404s indistinguishably from a missing one.
Authentication: CookieAuth, SupabaseAuth
Returns — limit · truncated · attempts · suggestions
Field names as the handler returns them; the source states no types for them.
Effect — reads analysis_attempts · annotation_categories · annotation_review_decisions · annotation_suggestions · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string | |
limit |
query | no | integer |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Suggestions with their latest review decision, if any. | |
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.21 updateImageTags
PATCH /api/images/{id}/tags
Replaces the tags on one image, so a reviewer can correct or add to what a detector labelled it with.
Why it exists. ‡ Replace the tags on one image, so a reviewer can correct or add to what a detector labelled it with.
Called by — modules/gallery/overlays/components/image-metadata-panel.tsx
Reached from — /datasets/<slug> · /w/data-explorer/anomalies/map · /w/data-explorer/assets · /w/integrity/assets · /w/integrity/evidence-explorer
Updates the tags for a specific image. Access is checked under RLS (404 for an image outside the caller’s datasets); the write itself is service-role since images has no UPDATE policy.
Authentication: CookieAuth, SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — success
Field names as the handler returns them; the source states no types for them.
Effect — writes (update) images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Request body
| Field | Type | Required |
|---|---|---|
tags |
array of string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
Updated. | |
400 |
Invalid request. | Error |
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
500 |
Server error. | Error |
19.0.22 getImageThumbnail
GET /api/images/{id}/thumbnail
Redirects to a small preview of one photograph, generating it on demand when none exists yet.
Why it exists. ‡ Return a small preview of one photograph, generating it on demand when none exists, so a gallery never renders a permanent blank for imagery that was simply never processed.
Redirects to the image’s 300×300 thumbnail. If the thumbnail does not exist yet it is generated on demand — resized from the original, stored in the thumbnails bucket, and recorded on images.thumbnail_url — and the redirect then points at the fresh URL. This is the single path every surface uses as its <img> source, so a missing thumbnail self-heals on first view regardless of which surface asked for it. RLS-scoped: an image outside the caller’s datasets 404s. Generation reads the image as the caller but writes the derived thumbnail with a platform credential, so a viewer who can see the image still receives a generated thumbnail rather than a blank. On a generation error the response redirects to a neutral placeholder instead of yielding a broken image.
Authentication: CookieAuth, SupabaseAuth
Uses a service-role client (row-level security bypassed).
Returns — not readable from the source: the handler returns a value assembled at run time rather than a literal.
Effect — writes (update) datasets · images
Parameters
| Name | In | Required | Type | Notes |
|---|---|---|---|---|
id |
path | yes | string |
Responses
| Status | Meaning | Body |
|---|---|---|
302 |
Redirects to the thumbnail, or to a placeholder image if generation fails. | |
401 |
Not authenticated. | Error |
404 |
Image not found. | Error |
19.0.23 resolveMedia
POST /api/media/resolve
Host-facing: turns a batch of image ids into ready-to-analyze image bytes for the AI backends — each id is checked against the caller’s own access, and failures come back per-image.
Why it exists. † Hand an agent the prepared bytes of images it names by id, so it can look at them without ever holding a storage path, SAS URL, or credential.
The transport for the Agent System hosts’ request-scoped resolve_media capability (ADR-006). Resolves a batch of image UUIDs under the caller’s RLS scope into prepared media — validated, orientation-normalized bytes as base64 — or a per-UUID typed failure. An image outside the caller’s scope resolves not_found, indistinguishable from a missing one. The response never contains a SAS URL, storage path, or credential. Limits: 25 UUIDs per batch, 64MB per image, 150MP decode.
Authentication: SupabaseAuth
Returns — results
Field names as the handler returns them; the source states no types for them.
Request body
| Field | Type | Required |
|---|---|---|
imageIds |
array of string | yes |
Responses
| Status | Meaning | Body |
|---|---|---|
200 |
One entry per requested UUID. | |
400 |
Missing/empty imageIds, or batch over the limit. | Error |
401 |
Not authenticated. | Error |