Skip to main content

Adjust Square integration

The final step of the ContentsVision workflow is pushing a reviewed inventory into Adjust Square, the claims platform. This is handled by the submit endpoint (POST /api/claims/{id}/submit-to-adjust-square) and a thin API client in backend/app/services/adjust_square.py.

Enabled per team; one button or a "Send to" menu

Adjust Square sending is gated by a per-team feature flag (features.adjust_square on /api/auth/me, toggled from the admin Teams tab) and is off by default — a team opts in. The claim page shows a single Send for Estimation button when only Adjust Square is enabled, or a Send to dropdown when Contents Estimation is enabled too. After submission the Submitted badge deep-links into the assignment at {ADJUST_SQUARE_APP_URL}/lkq/{claim_key} (plain text when ADJUST_SQUARE_APP_URL is unset). See Contents Estimation → Which teams can send.

What submission does

A submission takes the claim's selected rooms, builds an Adjust Square payload, creates or updates the remote claim and its items, uploads every photo on each item, and triggers Adjust Square's own AI processing of the claim.

First submission vs re-submission

The endpoint behaves differently depending on whether the claim has been submitted before (it checks for a stored adjust_square_claim_key).

First submission (synchronous create, background photos)

One create-full call carries the claim and every selected item atomically, so the request stays fast. The returned remote claim key and item ids are stored locally, items are marked submitted, and then a background task uploads the item photos and calls triggerFullLookup to start Adjust Square's processing.

Re-submission (background per-item)

On a claim that was already submitted, only the still-unsubmitted items are sent, one at a time via createItemForClaim, so a second batch adds only new items to the original claim. Because per-item calls are slow (roughly one second each), this runs entirely in a background task: the request returns immediately and items are marked submitted one by one as they land. Rooms that are new since the last submission are created first (synchronously) because createItemForClaim, unlike create-full, does not auto-create rooms.

Every photo, not just the primary

An item can hold several photos (the item_photos table; is_primary marks the one shown as the thumbnail). Both submission paths build their upload jobs from _item_upload_photos(item), which resolves every ItemPhoto to a local file (primary first, then photo_order) and falls back to the legacy single photo_path only for old items with no item_photos rows. Each photo is one POST /api/v1/media call attaching to the same remote item id. (Before ticket AS-742 only the primary photo_path was uploaded, silently dropping the rest.)

The Adjust Square endpoints used

ContentsVision client functionAdjust Square endpointUsed for
list_shopping_profilesGET /api/v1/shopping-profilesPopulate the submit modal's shopping-profile selector.
list_depreciation_schedulesGET /api/v1/depreciation-schedulesPopulate the submit modal's depreciation-profile selector.
create_full_claimPOST /api/v1/claims/create-fullFirst submission: claim + all items atomically.
create_item_for_claimPOST /api/v1/items/createItemForClaim/{claim_key}Re-submission: add one item to an existing claim.
list_claim_roomsGET /api/v1/rooms/by-claim/{claim_key}Find which rooms already exist.
create_roomPOST /api/v1/roomsCreate a missing room (re-submission).
update_itemPUT /api/v1/items/{item_id}Attach room_id to a freshly created item.
upload_item_photoPOST /api/v1/mediaUpload one item photo (called once per photo on the item).
list_item_media / item_media_filenames / item_has_mediaGET /api/v1/mediaRead an item's attached media. Powers the idempotent photo retry (existence check) and the backfill script (filename reconciliation, so a repair never duplicates a photo already on the portal).
trigger_full_lookupPOST /api/v1/claims/triggerFullLookup/{claim_key}Start Adjust Square's AI processing.

Field mapping

ContentsVision stores human-readable claim values and maps them to Adjust Square's integer codes in claims.py before sending:

ContentsVision valueAdjust Square fieldMapping
coverage_type (e.g. "RCV", "HO-3", "ACV")coverage_type_COVERAGE_TYPE_MAP (ACV/Commercial → 1, replacement-cost types → 2).
claim_type (e.g. "Contents", "Commercial")claim_type_CLAIM_TYPE_MAP (Commercial → 1, others → 2).
loss_type (e.g. "Fire", "Flood / Water Damage")peril_PERIL_MAP (Fire → fire, Flood → water, Tornado/Hurricane/Hail → wind, Theft → theft, else other).
shopping_profile_id (chosen in the submit modal)preferred_vendor_profilePassed through as the integer profile id.
depreciation_schedule_id (chosen in the submit modal)depreciation_schedule_idPassed through as the integer schedule id.

Items map their name, quantity, room, brand, age, notes (notes plus model/serial/condition), and estimated price. The ContentsVision item number is copied verbatim as a string so the two systems stay in sync.

Pricing profiles

The submit modal has a profile step where the user picks a shopping profile (vendor pricing) and a depreciation profile (depreciation schedule) for the claim. The selectors are filled from GET /api/claims/{id}/adjust-square-profiles, which proxies the team-scoped shopping-profiles and depreciation-schedules lists. Adjust Square scopes both lists to the authenticated team, so a user only ever sees their own team's profiles, and each list's default profile is preselected.

These only take effect on the first submission, when the claim is created via create-full (they become preferred_vendor_profile and depreciation_schedule_id on the claim payload). A claim's profiles are fixed once it exists, so on a re-submission the selectors are shown greyed out and the ids are not sent. When the user picks nothing, the fields are omitted and Adjust Square auto-resolves the team default for each.

Authentication: per-user

Calls to Adjust Square are authenticated as the team of the signed-in user:

  • Bearer token = the user's team.team_token (set from the SSO JWT on login).
  • Adjuster id = the user's user.external_id.

Both are read from the database at submit time and threaded through every Adjust Square client call, so a submission acts as the actual person and team that made it. The only required environment configuration is the Adjust Square base URL (ADJUST_SQUARE_BASE_URL); ADJUST_SQUARE_BEARER_TOKEN is an optional fallback for any call made without a team token and is not the "is the integration configured" gate. There is no longer a shared adjuster-id env var. If the user's team has no token, the submit endpoint returns 409 before making any call.

Implementation status

Per-user authentication is implemented: the team token and the user's external_id are threaded through the adjust_square service and every background task (claim create, re-submission, photo upload, retry, trigger-lookup). The old shared ADJUST_SQUARE_ADJUSTER_ID env var has been removed. The configured-gate is now just ADJUST_SQUARE_BASE_URL; ADJUST_SQUARE_BEARER_TOKEN remains only as an optional fallback. See the configuration reference.

Error handling

Every client call converts failures into AdjustSquareError, which carries the HTTP status and response body. Network failures (timeout, reset, DNS) are caught and reported as "unreachable" rather than bubbling up as an opaque 500.

SituationResult
Adjust Square not configured (no base URL)AdjustSquareError before any network call.
No team token and no env fallback tokenAdjustSquareError from header building (the submit endpoint catches the missing team token earlier as a 409).
Team has no team_token (per-user auth)Submit endpoint returns 409.
Adjust Square rejects the claim/item (non-2xx)AdjustSquareError with status + body; the synchronous path returns 502.
A single item fails during re-submissionLogged; that item stays unsubmitted so the user can retry; the rest continue.
Photo upload failsLogged; does not fail the submission (claim + items already exist).

The synchronous create path surfaces errors to the user as a 502; background failures are logged but do not surface, since the claim and items are already created and can be retried.

Backfilling missing photos (repair script)

backend/scripts/backfill_item_photos.py re-uploads photos that never reached Adjust Square, for claims submitted before the multi-photo fix (AS-742) or any item whose extra-photo upload later failed. It is the operator's tool for "the primary photo is on the portal but the additional images are missing".

Run it from backend/:

venv/bin/python scripts/backfill_item_photos.py --job <JOB_ID> # dry run
venv/bin/python scripts/backfill_item_photos.py --job <JOB_ID> --commit # upload
venv/bin/python scripts/backfill_item_photos.py --claim <CLAIM_ID_OR_KEY> --commit
venv/bin/python scripts/backfill_item_photos.py --item <ITEM_ID> --commit
  • Dry run by default. Without --commit it only prints what it would upload. Always dry-run first.
  • Never duplicates. For each submitted item it reads the item's attached media (item_media_filenames) and uploads only the local photos whose filename is not already on the portal. If the portal's filenames don't line up with ours at all (it renamed them), it falls back to a conservative count, assuming the first N in canonical order (primary first) are already there.
  • Auth. Uses each item's team token (item → job → room → claim → team), the same token a real submission uses. --token overrides it for a one-off.
  • The pure reconciliation decision lives in photos_to_backfill() and is unit-tested offline in tests/test_multi_photo_submission.py.