API Endpoint Inventory

Every operation the web app’s backend offers, in plain English, grouped by product area — one row each, so the table is the count.

This page is the complete list of everything the KAP screens can ask the application’s engine to do. Each row is one operation: the CODE on the left is its technical address (the action word — GET fetches, POST creates/sends, PATCH/PUT changes, DELETE removes — plus the path the browser calls, under /api); the right column says what it does in product terms. You do not need to understand the addresses to read this page.

Rows marked “no login” can be called without being signed in — all of them deliberately (a health check has to work before anyone logs in; the login entry points are how you sign in).

A security worklist of privileged operations that skipped the sign-in check was closed out on 2026-07-31. It did not stay closed. An audit on 2026-08-10 found six more — five of them added after that sweep — where the sign-in check was missing or too weak, including one where any signed-in user could delete another organization’s dataset and all its images by naming its slug. All six are fixed, and the rule they broke is now written down as CONTRACT-API-001 with a build-time check behind it, because a sweep only fixes the routes that exist on the day it runs. How this list is kept accurate — and what the underlying Swagger catalog is — is explained in Backend API & Swagger.

Annotations

No description written for this family yet — add one to FAMILIES['Annotations'] in scripts/api_inventory_prose.py.

Endpoint What it does
GET /annotation-categories Lists the whole vocabulary of annotation categories, so a reviewer can pick one when the AI’s wording matches nothing in it.
POST /annotation-categories Adds a condition class the shared vocabulary does not yet contain, so an inspection can name what it actually found rather than picking the nearest wrong label.
PATCH /annotation-categories/{id} Renames a condition class, or retires one that should no longer be offered — keeping every annotation and finding already recorded in its terms.
POST /annotation-review-decisions Records what a reviewer decided about one or more AI proposals — accept, reject, or recategorize — and, for the ones they accepted, turns them into real annotations in the same step. Decisions are added, never overwritten: a rejection later changed to an acceptance leaves both, with who did it and when. A proposal whose wording matches no category cannot be accepted until someone picks one.
GET /annotations Lists the annotations drawn on a specific photo.
DELETE /annotations/{id} Removes one stored annotation. The removal is recorded in the append-only edit log first — keeping the category, box and photo — so the audit survives the annotation. Editors only.
PATCH /annotations/{id} Reclassifies one stored annotation to another category. Only an editor of the owning organization may do it; the act is written to an append-only edit log.
POST /images/{id}/annotations Adds a new human-drawn anomaly to a photo from a rectangle and a category, recorded as a human annotation. Only an editor of the owning organization may do it; the act is written to an append-only edit log.

Anomalies

Analytics over what the annotations have found.

Endpoint What it does
GET /anomalies/dataset-distribution Charts anomaly counts per dataset.
GET /anomalies/label-distribution Charts how finding labels are distributed across an organization’s datasets.

Authentication

Signing in, and making sure every user belongs to an organization.

Endpoint What it does
GET /auth/check Answers “is this person signed in?”
POST /auth/create-organization Creates an organization for a signed-in user who has none.
POST /auth/create-organization-after-login Same, triggered right after login.
GET /auth/ensure-organization Verifies the user has an organization, creating one if needed.
POST /auth/generate-jwt-from-session Hands the chat layer the user’s current sign-in token (refreshing it if it is about to expire).
GET /auth/sso Lists the available enterprise single-sign-on providers (no login — it powers the login screen; currently returns an empty stub list). (no login)
POST /auth/sso Starts an enterprise single-sign-on login and returns where to redirect the user (no login — it is the login entry point). (no login)
POST /set-org-cookie Remembers which organization the user is currently working in.

CAD

Queries over the facility’s CAD model — the object database APS builds when it translates a Navisworks model for the 3D viewer.

Endpoint What it does
GET /datasets/{slug}/cad-objects Queries the extracted CAD model by property value — equipment, piping lines, plant unit, insulation, heat-tracing. cui_risk=true returns the lines that are both insulated and heat-traced (corrosion-under-insulation risk). Answers questions the 3D viewer cannot, straight from the design data.
GET /datasets/{slug}/cad/locate Says what stands at a GPS point in the CAD model: the point in CAD coordinates and the objects around it, nearest first, grouped by equipment tag — the world-to-CAD placement the render routes use, without rendering.

CAD Viewer (APS)

The bridge to Autodesk’s cloud service that converts and displays CAD models in the browser.

Endpoint What it does
GET /aps/auth Issues the view-only token the browser CAD viewer needs from Autodesk (upload/write powers stay server-side).
GET /aps/model Returns the CAD model a campaign declares, ready for the viewer — or says it has none.
GET /aps/model/locate Locates an equipment tag in the dataset’s CAD model — returns the CAD object ids (for highlighting in the viewer) and the model’s description of it. Matches the tag against the equipment objects’ Equip no. Reads the property database the viewer translation already built.
GET /aps/status/{urn} Reports how far along that conversion is (only for models in your organization).
POST /aps/translate Starts Autodesk’s conversion of a CAD file into a viewable format (only for models in your organization).
POST /aps/upload Uploads a CAD file to your organization’s own Autodesk storage for viewing (you must be a member).

Chat

The live connection between the assistant panel and the AI backend.

Endpoint What it does
GET /ag-ui-chat Reports the chat endpoint’s capabilities (no login; static status information). (no login)
POST /ag-ui-chat Sends a chat message to the AI and streams the reply back live — the main assistant pipeline (no login at this door; identity travels inside the message). (no login)
POST /mcp-chat The older, legacy chat pipeline, kept for compatibility (no login at this door). (no login)
GET /systems Lists the available AI agent systems the user can choose from (no login; falls back to a built-in list if the AI backend is unreachable). (no login)

Chat Sessions

Saved conversation history.

Endpoint What it does
GET /chat-sessions Lists your 50 most recent conversations.
POST /chat-sessions Starts a new saved conversation.
GET /chat-sessions/verify Produces a health report on your stored conversations (counts, anomalies).
POST /chat-sessions/verify Cleans up broken conversation data (empty sessions, orphaned messages).
DELETE /chat-sessions/{sessionId} Deletes a conversation and its messages.
GET /chat-sessions/{sessionId} Opens one conversation with all its messages.
PATCH /chat-sessions/{sessionId} Renames a conversation.
GET /chat-sessions/{sessionId}/export Downloads a conversation transcript as a spreadsheet (CSV) or document (Markdown).
POST /chat-sessions/{sessionId}/messages Adds a message to a conversation (the first message becomes its title).

Datasets

A dataset is a collection of inspection imagery and related files from one campaign — the core object explorers create, browse, and manage.

Endpoint What it does
GET /coco-auto-mapper Shows usage instructions for the annotation-import tool below. (no login)
POST /coco-auto-mapper Imports externally produced (COCO-format) annotations onto a dataset’s images by matching filenames.
GET /datasets Lists your datasets, optionally just those in one organization.
POST /datasets Creates a new dataset.
POST /datasets/create Creates a dataset in one of your organizations (membership enforced by the database’s row rules).
POST /datasets/list-bucket Previews the files inside an Azure storage container during onboarding (first 200 items).
POST /datasets/onboarding Creates a dataset from the Data Explorer onboarding wizard, storing its Azure connection details.
POST /datasets/update Updates a dataset’s settings (dataset editor) or its saved 3D camera position (3D viewer), dispatching on request content type. Requires a session; the caller must own the dataset or hold an admin/owner role in its organization.
POST /datasets/verify-storage Checks that the Azure storage credentials entered in the onboarding wizard actually work, without saving them.
DELETE /datasets/{slug} Deletes a dataset and its image records.
GET /datasets/{slug} Shows one dataset’s details.
PATCH /datasets/{slug} Edits a dataset’s name, description, visibility, or cover image.
GET /datasets/{slug}/3d-config Fetches the dataset’s saved 3D scene setup (models, camera position).
PUT /datasets/{slug}/3d-config Saves the dataset’s 3D scene setup.
GET /datasets/{slug}/categories Summarizes which annotation categories appear in the dataset, with counts and sample thumbnails.
POST /datasets/{slug}/find-by-content Finds which of a dataset’s photographs a given picture is — the frame a slide or a report cut it from — by perceptual hash, nearest first, with the Hamming distance stated so a near miss is not read as a hit.
GET /datasets/{slug}/geodata Same GPS listing via the newer route the 3D map uses.
GET /datasets/{slug}/gps-images Lists the dataset’s images that have GPS coordinates (for map views).
GET /datasets/{slug}/groups Lists those photo pairs grouped by location for the paired viewer.
POST /datasets/{slug}/image-url Issues short-lived secure links to view one image — or a batch of them — in Azure storage. The dataset is authorized under the caller’s own scope before any lookup; inaccessible datasets 404 like missing ones, and cross-dataset or unknown image ids are rejected per-id.
GET /datasets/{slug}/images Lists a dataset’s images, page by page, with filters.
POST /datasets/{slug}/index-images Scans the dataset’s storage container and registers the images it finds.
PATCH /datasets/{slug}/lifecycle Sends a campaign for review, or returns one for more evidence — the hand-off between Data Explorer and Integrity.
GET /datasets/{slug}/lifecycle-events Campaign History’s real transition timeline — who sent or returned the campaign, when, and why.
GET /datasets/{slug}/pairs Lists matched pairs of regular and thermal photos, page by page.
GET /datasets/{slug}/progress The same per-campaign numbers the /progress dashboard shows — coverage, tagged/anomaly counts, and new/confirmed/dismissed candidates.
POST /datasets/{slug}/sign-video Issues a short-lived secure link to play a video from the dataset.

Debug

Development-only diagnostics — not part of the product surface, listed here for completeness. “Development-only” is enforced, not just described: every endpoint below calls debugGuard() and returns 404 when NODE_ENV=production. The spec marks them x-environment: development, and a test holds that marker to the set of handlers that actually call the guard, in both directions.

Endpoint What it does
GET /debug/create-organization-test Developer test of the organization-creation flow.
GET /debug/fix-organization Developer repair tool for broken organization memberships.
GET /debug/organization-status Developer check of a user’s organization membership state.
GET /debug/organizations Developer dump of a user’s organization details.
GET /debug/organizations-data Developer dump of detailed organization data.
GET /debug/user-organization Developer dump of combined user and organization information.
GET /test-image-visualization Developer harness that generates a sample chat image event (no login, development only). (no login)
POST /test-image-visualization Developer harness that simulates a chat image-analysis sequence (no login, development only). (no login)

Equipment

The Integrity workspace’s asset register: physical equipment, AI findings against it, and the links between photos and assets.

Endpoint What it does
GET /damage-mechanisms Lists the API-571 damage mechanisms, so an engineer can ground a finding in a recognised industry mechanism rather than only in a class somebody already drew.
GET /equipment Lists the organization’s equipment with finding and photo counts.
GET /equipment-damage-mechanisms For an organisation, every finding raised against its assets; for one photo, the findings it evidences and its geo link; for one campaign, which of its images are already decided.
POST /equipment-damage-mechanisms Creates a new finding on an asset — grounded in exactly one of an API-571 damage mechanism or an inspection observation — stamped to the engineer who raised it. A second finding on the same asset and grounding is allowed.
DELETE /equipment-damage-mechanisms/{id} Deletes a finding that was raised in error — a test row, a duplicate, one on the wrong asset — writing a tombstone first so the register can be corrected without the correction being invisible.
PATCH /equipment-damage-mechanisms/{id} Corrects a finding after it was raised — amends the engineer’s notes, or moves it to the condition class it should have claimed — recording what it said before in an append-only edit log.
PATCH /equipment-damage-mechanisms/{id}/decision Records a human verdict on a finding — confirm it, dismiss it, or reinstate one that was dismissed — as a row in an append-only decision log holding the deciding engineer, the time and the required reasoning. A revised verdict links to the one it supersedes; both are kept.
PATCH /equipment-damage-mechanisms/{id}/evidence Adds or removes the images a finding rests on, so a claim already raised can gather more support instead of being raised twice.
GET /equipment/image-links Every active image-to-asset link in one campaign, keyed by image id, so a candidate queue can group by equipment without one call per image.
GET /equipment/positions Gives the 3D campaign overview the map coordinates of the organization’s geotagged equipment, with finding/photo counts for marker styling and the matching CAD-local position where known.
GET /equipment/{id} Shows one asset in full: its details, its engineering data sheet (lining, design conditions and nozzle schedule), its photos split into the images of this asset — those tagged as showing it, and those whose nameplate reads its tag but that no person has approved yet — and a nearby set (linked by camera position only, each with the equipment tag its placard OCR suggests), and damage findings with their photographic evidence. Each photo names the anomalies on it — the classes its boxes were drawn under, and its damage-descriptor tags — so the asset page can list what was found on the asset and near it. Data-sheet fields are null when nothing is recorded, and the panel says “Not recorded” rather than guessing.
DELETE /images/{id}/asset-link Unlinks a photo from its asset, keeping an audit record of who removed it and why.
GET /images/{id}/asset-link Shows which asset a photo is linked to, how that was established, and how confidently.

Gas Readings

Georeferenced gas-sensor measurements alongside the imagery.

Endpoint What it does
GET /datasets/{slug}/gas-readings Lists all gas readings recorded for a dataset, in time order.
GET /datasets/{slug}/has-gas-readings Answers “are there gas readings near this dataset?” (within 10 km).
GET /gas-readings Lists the gas readings from the campaigns you can see.
GET /images/{id}/gas-readings Finds the gas readings taken nearest to a specific photo, if the photo is one you can see.

Images

Searching, inspecting, and enriching the photo library.

Endpoint What it does
GET /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.
POST /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.
GET /images/candidates Lists a campaign’s review candidates — tagged images awaiting engineering attention — with their review state and asset attribution.
GET /images/exif Reads the EXIF block out of one stored image — camera, GPS, timestamp — for inspecting what the camera actually recorded.
POST /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.
GET /images/extract-metadata Extracts and stores one image’s capture metadata — position, camera, timestamp — from the image itself.
POST /images/extract-metadata Extracts capture metadata for a batch of a dataset’s images in one pass, to backfill a dataset indexed without EXIF.
GET /images/search Searches a dataset’s images by filename, tag or description, with pagination and sorting, optionally restricted to GPS-tagged images.
GET /images/suggestions Suggests tag completions from the tags actually present in a dataset, so the search box offers terms that will return results.
GET /images/thumbnails Returns a dataset’s thumbnails for preview strips.
GET /images/{id} Fetches one image’s stored record — metadata, tags, and any analysis attached to it — optionally with the bytes inlined.
POST /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.
POST /images/{id}/analyze Dispatches one image to the AI gateway’s analysis skill and records the attempt with its suggestions.
DELETE /images/{id}/asset-tag Withdraws a confirmed asset tag, for a photograph attributed to the wrong asset.
POST /images/{id}/asset-tag Confirms that a photograph shows a particular piece of equipment, promoting an OCR-read suggestion to a confirmed tag.
PATCH /images/{id}/metadata Shallow-merges a partial object into one image’s stored metadata, without resupplying the whole object.
GET /images/{id}/ocr Returns the OCR text a prior run stored for one image — a cheap read, no model invoked.
POST /images/{id}/ocr Dispatches one image to the AI gateway’s text-reading skill and persists the result onto the image’s metadata.
GET /images/{id}/pair Finds the visual or thermal counterpart of one image, so the two frames of a capture can be viewed together.
GET /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.
PATCH /images/{id}/tags Replaces the tags on one image, so a reviewer can correct or add to what a detector labelled it with.
GET /images/{id}/thumbnail Redirects to a small preview of one photograph, generating it on demand when none exists yet.
POST /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.

MCP

No description written for this family yet — add one to FAMILIES['MCP'] in scripts/api_inventory_prose.py.

Endpoint What it does
GET /mcp The server→client stream slot; not offered in this stateless phase (answers 405). (no login)
POST /mcp The remote Model Context Protocol endpoint for external AI clients (e.g. Claude). Speaks JSON-RPC (initialize, list/call tools) and exposes a curated tool surface plus three spec-driven ones (kap_api_list, kap_api_show, kap_api_call) that discover and call any read in this specification; writes stay curated, and each tool runs as the signed-in user under RLS. Every payload it forwards is scrubbed of secret-keyed values (credentials, account keys, tokens) on the way out, because a tool returns whatever shape its handler returns. Off unless MCP_SERVER_ENABLED. See docs/proposals/20260816_remote_mcp_server.md.
GET /mcp/embed/find-by-content Renders a drop zone where the engineer can put a picture and see which of a dataset’s photographs it is (the target of the kap_find_image_by_content MCP UI resource). Gated by a short-lived sealed t token that carries the user’s bearer server-side. Off unless MCP_SERVER_ENABLED. (no login)
POST /mcp/embed/find-by-content The drop-zone page’s search call: forwards the posted picture to searchImagesByContent for the token’s dataset, as the user the token was sealed for, and returns that operation’s answer. (no login)
GET /mcp/embed/gallery Renders a dataset’s image gallery as a self-contained HTML page for a host to embed in a sandboxed iframe (the target of the kap_gallery_ui(hosted:true) MCP UI resource). Gated by a short-lived sealed t token that carries the user’s bearer server-side; images are fetched under RLS. Off unless MCP_SERVER_ENABLED. (no login)
GET /mcp/oauth/authorize OAuth 2.1 authorization (PKCE). Authenticates via the existing Supabase session; when signed in it renders a consent page naming the client (no code issued here), and bounces to /login when not. (no login)
POST /mcp/oauth/authorize The consent decision. Only an explicit approval carrying a signed, session-bound consent token issues a code — this is what prevents silent code issuance to an arbitrary registered client. Denial/forgery returns access_denied or 400. (no login)
POST /mcp/oauth/register Dynamic client registration (RFC 7591). The client’s metadata is HMAC-signed into a stateless client_id; redirect_uris must be absolute http(s) URLs. Public clients (PKCE, no secret). Off unless MCP_SERVER_ENABLED. (no login)
POST /mcp/oauth/token Exchanges a PKCE authorization code (or a refresh token) for the user’s Supabase access token, returned as the OAuth access token so the resource server verifies it with the existing requireUser. (no login)

Notes

Field notes pinned to locations, with optional PDF attachments.

A note is deliberately the platform’s general object: a body, a place, and a tag. The tag is what makes one a work order rather than an observation or a recommendation, and the vocabulary of tags belongs to each organisation — created, renamed and retired by its own engineers rather than fixed by the platform. A tag carries a slug that never changes, because that is the value an external system maps on, and a label that may be renamed as often as a customer likes.

Endpoint What it does
GET /datasets/{slug}/notes Lists the notes on one dataset.
POST /datasets/{slug}/notes Creates a note on a dataset.
DELETE /datasets/{slug}/notes/{noteId} Deletes a note and its attachment (creator or an admin of its organization).
GET /datasets/{slug}/notes/{noteId} Opens one note (with a fresh one-hour link to its PDF, if any).
PATCH /datasets/{slug}/notes/{noteId} Edits a note (creator or an admin of its organization).
DELETE /datasets/{slug}/notes/{noteId}/pdf Removes a note’s PDF attachment (creator or an admin of its organization).
POST /datasets/{slug}/notes/{noteId}/pdf Attaches (or replaces) a PDF on a note — PDF only, up to 50 MB (creator or an admin of its organization).
GET /note-tags List an organisation’s note vocabulary — the tags that say what kind of note something is.
POST /note-tags Name a kind of note this organisation’s vocabulary does not yet contain.
PATCH /note-tags/{id} Rename a note tag, or retire one that should no longer be offered. The slug cannot be changed.
GET /notes Lists all location notes you can see, across datasets.

Organizations

Managing organizations and their members.

Endpoint What it does
POST /organizations/add-member Adds a person to an organization with a role (admins only).
POST /organizations/create Creates a new organization.
POST /organizations/create-for-user Creates a fresh organization for yourself (self-service only; creating one for another user would need a platform-admin role, which doesn’t exist yet).
POST /organizations/delete Deletes an organization (admins only).
GET /organizations/get-members Lists an organization’s members, with their email addresses.
GET /organizations/get-user-organizations Lists the organizations you belong to, and your role in each one.
GET /organizations/image-totals Org-wide image totals, deduplicated across campaigns that share images.
POST /organizations/remove-member Removes a person from an organization (admins only).
POST /organizations/rename Renames an organization (admins only).
POST /organizations/update-member-role Changes an existing member’s role — viewer, member or admin. An administrator can change other people’s roles but not their own, so an organization is never left without one.

Render

No description written for this family yet — add one to FAMILIES['Render'] in scripts/api_inventory_prose.py.

Endpoint What it does
GET /datasets/{slug}/render/cad Renders the dataset’s Navisworks model from a camera pose, and can identify objects.
GET /datasets/{slug}/render/scene Renders the dataset’s Gaussian splat and CAD mesh from a camera pose.

Reports

Generated inspection reports, and the short-lived links that let a browser download one.

Endpoint What it does
GET /reports Lists the caller’s PDF reports (newest first), optionally filtered to one campaign by dataset_slug or one asset by asset_tag.
POST /reports Generate a report from a set of findings and notes an engineer has selected, so the write-up cites its evidence instead of restating it from memory.
GET /reports/{id}/download Issues a short-lived secure link to download a report’s PDF. Only the report’s owner sees it, unless they have shared it with their organization.

Storage

Direct operations on the cloud file store behind the datasets.

Endpoint What it does
POST /storage/create-container Creates a new container in the customer’s storage account for a dataset that needs somewhere to put its files.
POST /storage/delete Deletes one file from a customer’s storage container.
POST /storage/download Streams one file out of a customer’s storage container to a caller entitled to the dataset it belongs to.
POST /storage/list-files Lists one page of the files in a customer’s storage container.
POST /storage/metadata Reads one stored file’s size, type and last-modified time, without transferring the file.
GET /storage/reference-files Lists one folder level of a dataset’s linked Google Cloud Storage reference bucket, if it has one.
GET /storage/reference-files/content Reads one text, markdown, PDF or image file’s content from a dataset’s linked Google Cloud Storage reference bucket.
POST /storage/upload-url Mints a short-lived link that lets the browser upload a file straight into the customer’s container.
POST /storage/validate-azure Checks that a set of Azure storage credentials work, by using them, and reports precisely what failed.

System

No description written for this family yet — add one to FAMILIES['System'] in scripts/api_inventory_prose.py.

Endpoint What it does
GET /health Answers “is the application alive and are its dependencies reachable?” (no login — monitoring calls this before anyone signs in). (no login)
POST /idms/push Placeholder for pushing data to an IDMS system — accepts and acknowledges, nothing more yet.
POST /workspaces/events Records a workspace usage event in the server logs.

Thumbnails

Generating the small preview images the galleries scroll through.

Endpoint What it does
POST /thumbnails/generate Creates the preview thumbnail for one photo.
POST /thumbnails/generate-bulk Creates thumbnails for every photo in a dataset that lacks one, in controlled batches.
POST /thumbnails/generate-video Creates a preview image from a video by capturing a frame.

TTS

No description written for this family yet — add one to FAMILIES['TTS'] in scripts/api_inventory_prose.py.

Endpoint What it does
POST /tts/synthesize Turns assistant text into spoken audio via Google Cloud, falling back to the browser’s built-in voice if unavailable.

Worklist

A single feed of what is outstanding across a campaign — findings and notes, merged and sorted by what changed most recently.

Findings and notes are raised through their own families above; this is the one place they are read together. It answers “what is outstanding here” without requiring two screens, and it is scoped to an organisation rather than one dataset, because a finding has no first-class link to a single campaign the way a note does.

Endpoint What it does
GET /worklist List what is outstanding across a campaign — the findings raised against an organisation’s assets and the notes filed on its datasets — as one feed sorted by what changed most recently.

168 operations across 136 paths, 23 families, 19 of them callable without a login. Generated from web/public/api-docs/swagger.yaml plus each zod-converted family’s own web/openapi/<family>.json (datasets, images, equipment, notes, organizations, storage, chat-sessions, annotations, authentication, mcp, debug, cad-viewer-aps, cad, render, gallery, anomalies, tts, reports, gas-readings, chat, thumbnails, system, worklist), which scripts/check_swagger_drift.py holds level with the route handlers in web/app/api/ on every change — so a route that exists without a row here fails the build, and a row here without a route does too.