{
  "openapi": "3.0.3",
  "info": {
    "title": "EvalExpert API",
    "description": "Appraisals, vehicle checks, and evaluation webhooks. Interactive docs: https://developer.algodriven.xyz/evalexpert\n\n**Markets.** One EvalExpert pack covers every region; behaviour follows the key.\n- **Australia** — plate lookups take an AU state (`NSW`, `VIC`, …). Ambiguous VINs return an options array (`options` / `VechielDescriptions`); choose with `desc` on `/manualcarentry`. PPSR / Car History (`/ppsrsearch`, `/carhistory`, …) are AU-only.\n- **United States** — plate lookups take a 2-letter state (`CA`, `TX`, …). Ambiguous VINs return `{ descriptionSelect: true, descriptionOptions: [{ code, codeType, … }] }` with an `EvalID` already created; choose with `code` + `codeType` (`chrome` or `blackbook`) on `/manualcarentry`. Sandbox quota returns `429` when real-lookup limits are hit.\n- **GCC / MENA** — disambiguation mirrors US (`code` / `codeType`). `codeType` is `ADData`; `code` is the 13-character ADCode. `gccPriceGuide=true` on `/dcupdateeval` returns GCC pricing on the response.\n\n**Accessing a dealer.** Two ways to run a request as a given rooftop:\n- **Legacy — dedicated API key.** Issue one key per rooftop. The key is bound to that dealer, so every call already runs as that rooftop. Do not send `X-Dealer-Id`. Existing single-dealer integrations keep working this way.\n- **New — `X-Dealer-Id` header.** Issue one partner key that covers several rooftops. Send `X-Dealer-Id` with that rooftop's `uid` (not the numeric DealerID) on every request. Required when the key is not bound to a single dealer; ignored on a dedicated key. The key must be authorised for that rooftop.\n\n**Creation flow** (an eval is not visible in the apps until it is Complete):\n1. `POST /entervin` — start from a VIN or plate. Returns an `EvalID`, or style options when the VIN is ambiguous.\n2. `POST /manualcarentry` — only if `/entervin` does not return a single vehicle. Send the chosen `desc` (AU) or `code` / `codeType` (US / GCC). Can also start from make/model without a VIN.\n3. `POST /dcupdateeval` — required. Sets mileage, recon, customer, salesperson (`userEmail` from `/getuserlist`), and appraisal amount (`price`). First call marks the eval Complete; later calls overwrite.\n4. `POST /imageupload` — optional photos, one file per call (`order`, `hero`). `POST /imagelist` reads them back with their ids at any stage; `POST /imageorder` / `POST /imagehero` then reorder or pin the hero. Gallery order and the hero are independent: reordering never moves the hero, and pinning never moves a photo in the gallery. With nothing pinned the hero is the first photo in display order. `/geteval` includes `Photos` alongside `CarPics`.\n5. `POST /geteval` — retrieve a completed appraisal. Same shape as the webhook body.\n\n**Pricing AI:** `GET /pricingAi/payload/{evalid}` after the eval is Complete with mileage. Pass `reasoning=1` and poll until `reasoning.status` is `completed`.\n\n**Vehicle checks (AU keys only):** attach PPSR / Car History to an existing EvalID (`/ppsrsearch`, `/carhistory`). The VIN is taken from the eval. The eval does not need to be Complete. Then `/getpastppsrsearch`, `/getpastcarhistory`, `/ppsrwatch`.\n\n**Webhooks:** provide an HTTPS POST URL, optional custom auth headers, and a trigger. Register with `POST /partner/webhooks`. The delivered body is the `/geteval` payload. Partner-manageable eval triggers: `ON_COMPLETE` (new eval finished), `ON_EXPORT` (Export pressed in the app). `ON_SHARE_OFFER` (offer/price added on a shared eval) exists but is not self-serve on this CRUD.\n\n**Dates:** `EntryDate` is stored UTC (`YYYY-MM-dd hh:mm:ss`). `/getevallist` `startdate` / `enddate` are Unix seconds in UTC.\n\n**AU disambiguation sample VINs:** `1C6RR7FT6MS537299`, `MMAJLKL10PH006556`, `6G1EP5EH6AL460983`. Do not send these on a US key.",
    "version": "1.3.0",
    "contact": {
      "name": "AlgoDriven",
      "url": "https://algodriven.xyz/developers/evalexpert-api/"
    }
  },
  "servers": [
    {
      "url": "https://evalexpert.io/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Evaluations",
      "description": "Create, complete, and retrieve appraisals."
    },
    {
      "name": "Users",
      "description": "Dealer-group users. Use Email as userEmail on /dcupdateeval."
    },
    {
      "name": "Pricing AI",
      "description": "Wholesale / retail range and optional async reasoning for a completed eval."
    },
    {
      "name": "Vehicle Checks",
      "description": "AU only. PPSR Search and Car History attached to an EvalID."
    },
    {
      "name": "Webhooks",
      "description": "Outbound HTTPS callbacks. Body is the /geteval payload."
    }
  ],
  "paths": {
    "/entervin": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Start an evaluation by VIN or plate",
        "description": "Creates an in-progress evaluation. The eval is not visible in the apps until `/dcupdateeval` marks it Complete.\n\nSend a 17-character VIN, or a plate plus `state`. Two response shapes:\n- A decoded vehicle with `EvalID` — continue to `/dcupdateeval`.\n- Style options when the VIN is ambiguous. **AU:** an array whose item has `options` / `VechielDescriptions` and no `EvalID`; POST the chosen `Description` as `desc` to `/manualcarentry`. **US:** a single object `{ VIN, EvalID, descriptionSelect: true, descriptionOptions: [ { code, codeType, fullDescription, highlight } ] }` — detect it with `descriptionSelect === true`, not by a missing `EvalID` (the US flow creates the eval first, so `EvalID` is always populated); POST `evalid` plus the chosen option's `code` and `codeType` to `/manualcarentry`. **GCC:** as US — echo the chosen option's `code` and `codeType`.\n- Not accepted. Read the body rather than the status code: an undecodable VIN returns an object with an `error` string and neither `EvalID` nor a disambiguation payload, and it does so under a 2xx status (201 for a live decode, 200 for the canned AU sample VINs below). The API does not validate the VIN check digit; \"invalid check digit\" messages come from client-side validation.\n\nEvery evaluation response carries `CreatedAt`, `CompletedAt` (null until completed) and `UpdatedAt` as ISO-8601 UTC (`2026-09-04T22:40:29Z`). Prefer these over `EntryDate`, which has no zone designator.\n\nSample VINs: AU keys — `6G1EP5EH6AL460983`, `1C6RR7FT6MS537299`, `MMAJLKL10PH006556` (ambiguous) and `5YJSB7E24JF257977` (single match), all answered from canned AU data. US keys — `1FTFW1EF2HFB45917`, `JF1VBAF62P9812865`, `1C4RJFBG9KC749141` (single match), `4T1BZ1HK4KU025338`, `SALVP2BG6HH236343` (two styles each); do not use the AU list on a US key.",
        "operationId": "enterVin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "vin"
                ],
                "properties": {
                  "vin": {
                    "type": "string",
                    "description": "17-character VIN, or a registration plate (then `state` is required)."
                  },
                  "state": {
                    "type": "string",
                    "description": "Jurisdiction for a plate lookup. 2–3 letter abbreviation, uppercase, no spaces or periods (e.g. NSW, WA, CA, TX)."
                  }
                }
              },
              "example": {
                "vin": "WMWXS520X02F23890"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "A decoded evaluation (has EvalID), a disambiguation payload (options / VechielDescriptions, or descriptionSelect on US keys), or `{ \"error\": \"...\" }` when the VIN could not be decoded. Every outcome is a 2xx, so inspect the body, not the status code. The canned AU sample VINs answer 200 instead of 201.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Invalid API key"
          },
          "429": {
            "description": "Sandbox real-lookup quota reached"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/manualcarentry": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Create an evaluation from a chosen vehicle spec",
        "description": "Use when `/entervin` returns more than one style, or to start from make/model without a VIN.\n\n**AU:** send `vin` + `desc` (the chosen `Description` string).\n**US / GCC:** send `evalid` plus the chosen option's `code` and `codeType`; the existing EvalID is updated in place. On US keys, if `codeType` is omitted it is inferred from the `/entervin` decode already held for the VIN, and if `evalid` is also omitted the caller's most recent in-progress evaluation for that VIN with no style chosen yet is used. Sending all three is still recommended. GCC can also start with `code` (the ADCode) and `codeType` = `ADData`.\n\nA successful response looks like a decoded `/entervin` result and includes an `EvalID`.",
        "operationId": "manualCarEntry",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vin": {
                    "type": "string"
                  },
                  "desc": {
                    "type": "string",
                    "description": "AU. Chosen Description from /entervin"
                  },
                  "evalid": {
                    "type": "integer",
                    "description": "EvalID from an /entervin clarification response"
                  },
                  "code": {
                    "type": "string",
                    "description": "US/GCC. Chosen option code (Chrome/Black Book style id, or the 13-character ADCode on GCC)"
                  },
                  "codeType": {
                    "type": "string",
                    "description": "US / GCC. Echo the chosen option's `codeType` from `/entervin`. US: typically `chrome` or `blackbook`. GCC: `ADData` (not `ADCode` — that string is the `code` value). Recommended; on US keys inferred from the cached `/entervin` decode when omitted."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evaluation created or updated (same shape as a successful /entervin, including CreatedAt / CompletedAt / UpdatedAt)"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/dcupdateeval": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Update evaluation details and mark it Complete",
        "description": "First call on an EvalID writes recon / customer / owner and marks the eval Complete so it appears in the apps. Later calls overwrite the same fields.\n\n**Appraisal amount:** `price` (also accepted as `finalprice` / `FinalPrice`). Stored as `FinalPrice` on `/geteval`. `UserPrices` is offer/guide history and is not filled by this field.\n\n**Salesperson:** `userEmail` from `/getuserlist`. `/entervin` has no user parameter, so the eval is created against the user linked to the API key; send `userEmail` on the first `/dcupdateeval` so it appears in that salesperson's app. Managers, dashboards, `/geteval`, and `/getevallist` see it either way. Sending `userEmail` on an already-complete eval reassigns it.\n\n**Mileage:** `km` is required on the first complete. **Condition:** integer 0–10 (0 unroadworthy, 10 showroom).\n\n**GCC:** `gccPriceGuide=true` returns GCC pricing data on the response.\n\n**Timestamps in the response:** `CreatedAt`, `CompletedAt` and `UpdatedAt` are ISO-8601 UTC, read after this call's writes, so `CompletedAt` is populated on the call that completes the eval. `EntryDate` here is the creation time in UTC with no designator and is kept for backwards compatibility.",
        "operationId": "dcUpdateEval",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid",
                  "km"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer",
                    "description": "EvalID from /entervin or /manualcarentry"
                  },
                  "km": {
                    "type": "integer",
                    "description": "Odometer. Required the first time this eval is completed."
                  },
                  "notes": {
                    "type": "string"
                  },
                  "condition": {
                    "type": "integer",
                    "description": "Condition score 0–10"
                  },
                  "vin": {
                    "type": "string"
                  },
                  "sparekey": {
                    "type": "integer",
                    "description": "1 = yes, 0 = no. Omit if unknown."
                  },
                  "service": {
                    "type": "integer",
                    "description": "6 Full Mixed, 5 Full Genuine, 4 Full Non-Genuine, 3 Partial, 2 Missing book / none. 1/0 are legacy."
                  },
                  "financed": {
                    "type": "integer",
                    "description": "1 = yes, 0 = no. Omit if unknown."
                  },
                  "financename": {
                    "type": "string"
                  },
                  "financeamt": {
                    "type": "integer"
                  },
                  "financepmt": {
                    "type": "integer",
                    "description": "Monthly repayment"
                  },
                  "recondetail": {
                    "type": "number"
                  },
                  "recontyres": {
                    "type": "integer",
                    "description": "Number of tyres to replace"
                  },
                  "reconrims": {
                    "type": "number"
                  },
                  "reconpaint": {
                    "type": "number"
                  },
                  "reconint": {
                    "type": "number"
                  },
                  "reconrego": {
                    "type": "number"
                  },
                  "reconscreen": {
                    "type": "number"
                  },
                  "reconrwc": {
                    "type": "number"
                  },
                  "wtymth": {
                    "type": "integer"
                  },
                  "wtyyear": {
                    "type": "integer"
                  },
                  "svcmth": {
                    "type": "integer"
                  },
                  "svcyear": {
                    "type": "integer"
                  },
                  "cName": {
                    "type": "string"
                  },
                  "cPhone": {
                    "type": "string"
                  },
                  "cEmail": {
                    "type": "string"
                  },
                  "cPCode": {
                    "type": "string"
                  },
                  "cState": {
                    "type": "string"
                  },
                  "cAddress": {
                    "type": "string"
                  },
                  "cSuburb": {
                    "type": "string"
                  },
                  "customFields": {
                    "type": "object",
                    "description": "Custom customer fields keyed by their exact configured names. Available names, types, options, and current values are returned by /geteval in customFields.",
                    "additionalProperties": { "type": "string" }
                  },
                  "price": {
                    "type": "integer",
                    "description": "Appraisal amount stored as FinalPrice"
                  },
                  "finalprice": {
                    "type": "integer",
                    "description": "Alias for price"
                  },
                  "FinalPrice": {
                    "type": "integer",
                    "description": "Alias for price"
                  },
                  "pricingAi": {
                    "type": "boolean",
                    "description": "Include the Pricing AI payload on the response"
                  },
                  "reasoning": {
                    "type": "boolean",
                    "description": "With pricingAi, include AI reasoning"
                  },
                  "userEmail": {
                    "type": "string",
                    "description": "Salesperson email from /getuserlist"
                  },
                  "gccPriceGuide": {
                    "type": "boolean",
                    "description": "GCC only. Return GCC pricing on the response."
                  }
                }
              },
              "example": {
                "evalid": 123456,
                "km": 42000,
                "price": 28750,
                "condition": 8,
                "userEmail": "jane.smith@exampledealer.com.au",
                "recondetail": 500
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Full appraisal after the update"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/imageupload": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Upload an evaluation photo",
        "description": "Attach an image to an existing EvalID. **One file per request** — call it once per photo.\n\nDisplay order is `order` when supplied, otherwise oldest `timestamp`, otherwise upload time. `order` is not unique and nothing renumbers: uploading `order: 2` when another photo already holds 2 leaves a tie, broken by upload time then image id. To get a guaranteed sequence, upload the photos and then send the full list to `/imageorder`.\n\n`hero: 1` (alias `firstPic: 1`) pins this photo as the hero permanently. Without it the hero is the first photo in display order — see the `Photo` schema for how `hero` resolves.\n\nMultiple files in one request are accepted only when neither `order` nor `hero` is set, because both apply to the request rather than to an individual file. Sending either alongside several files returns 400 rather than applying it to an arbitrary one.",
        "operationId": "imageUpload",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid",
                  "file"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "timestamp": {
                    "type": "integer",
                    "description": "Unix epoch used for display order when `order` is omitted. Also accepted as timeStamp."
                  },
                  "firstPic": {
                    "type": "integer",
                    "description": "1 = pin as the eval hero permanently (alias of hero)"
                  },
                  "order": {
                    "type": "integer",
                    "description": "Display order (1-based). Wins over timestamp when set. Not unique — equal values tie-break on upload time then image id."
                  },
                  "hero": {
                    "type": "integer",
                    "description": "1 = pin as the eval hero permanently (alias of firstPic)"
                  }
                }
              },
              "example": {
                "evalid": 123456,
                "order": 1,
                "hero": 1
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Image stored. `CarPics` is the evaluation's full photo list in display order, including the photo just added. Note this is the raw internal row rather than the `Photo` projection — use `/imagelist` for the documented shape.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "CarPics": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ID": {
                            "type": "string",
                            "description": "Image id. Pass this to /imageorder and /imagehero."
                          },
                          "URL": {
                            "type": "string",
                            "format": "uri"
                          },
                          "order": {
                            "type": [
                              "integer",
                              "string",
                              "null"
                            ]
                          },
                          "hero": {
                            "type": "boolean"
                          },
                          "heroSource": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "angle": {
                            "type": [
                              "string",
                              "null"
                            ]
                          }
                        },
                        "description": "Carries further internal columns (ext, recon, UploadTime, …) that are not part of the contract."
                      }
                    }
                  }
                },
                "example": {
                  "CarPics": [
                    {
                      "ID": "23942096",
                      "URL": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 1,
                      "hero": true,
                      "heroSource": "manual",
                      "angle": "exterior_front_three_quarter"
                    },
                    {
                      "ID": "23942097",
                      "URL": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 2,
                      "hero": false,
                      "heroSource": null,
                      "angle": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "No evalid, an evaluation this key may not write to, an unreadable or zero-byte image, or `order` / `hero` sent with more than one file. Unlike the other image endpoints this one reports an inaccessible evaluation as 400, not 403.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "order and hero apply to a single photo. Upload one file per request when using them."
                }
              }
            }
          },
          "403": {
            "description": "The key is not authorised for this endpoint or rooftop"
          },
          "500": {
            "description": "The image could not be stored"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/geteval": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Get a completed evaluation",
        "description": "Returns matching **completed** evaluations. One of `evalid`, `vin`, or `reg` / `registration` is required. Same shape as the webhook body.\n\n`user` is the user id or email to read **on behalf of** (must be in the same dealer group). It is not a customer field. If omitted, the user linked to the API key is used.\n\n`FinalPrice` is the appraisal amount set on `/dcupdateeval`. `UserPrices` is offer/guide history (empty array when none exist). `UserName` / `UserEmail` identify the salesperson. Eval Link captures use a generated account (`UserName` begins `Eval Link -`, `UserEmail` ends `.evallink@algodriven.xyz`); filter on that suffix, do not treat it as a mailbox.\n\n`reconditioning_list` is the same line items as the evaluation details page. `Reconditioning` stays the dollar total. A Tesla battery SoH reading is an item with `type` `info`, `source` `tesla`, `cost` 0, and `value` such as `93.3%`.\n\n`/retrieveevals` remains the in-app endpoint (single-user, `pastevals` envelope, 201). `/geteval` and `/getevallist` are the EvalExpert API product.",
        "operationId": "getEval",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "evalid": {
                    "type": "integer",
                    "description": "EvalID from /entervin"
                  },
                  "vin": {
                    "type": "string",
                    "description": "Returns every completed eval in the dealer group for that VIN"
                  },
                  "reg": {
                    "type": "string",
                    "description": "Registration / plate"
                  },
                  "registration": {
                    "type": "string",
                    "description": "Alias for reg"
                  },
                  "state": {
                    "type": "string",
                    "description": "Jurisdiction if looking up by plate"
                  },
                  "user": {
                    "type": "string",
                    "description": "User id or email to read on behalf of"
                  }
                }
              },
              "example": {
                "evalid": 123456
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Array of Evaluation objects (more than one when searching by vin or reg).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Evaluation"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Evaluation not found, or user was required and omitted"
          },
          "403": {
            "description": "Forbidden or invalid API key"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/getevallist": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List evaluations",
        "description": "List completed evaluations for the authenticated dealer group. `startdate` / `enddate` are Unix seconds in UTC compared against `EntryDate` (also stored UTC). Omit `enddate` to include the most recent. Do not send `from` / `to` date strings.",
        "operationId": "getEvalList",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "startdate": {
                    "type": "integer",
                    "description": "Unix timestamp (UTC seconds). Exclusive lower bound on EntryDate."
                  },
                  "enddate": {
                    "type": "integer",
                    "description": "Unix timestamp (UTC seconds). Exclusive upper bound. Omit for the most recent."
                  },
                  "user": {
                    "type": "string",
                    "description": "Restrict to one salesperson by user id or email"
                  },
                  "dealerId": {
                    "type": "string",
                    "description": "JSON array of dealership ids, e.g. \"[1234,1235]\""
                  }
                }
              },
              "example": {
                "startdate": 1756800000,
                "enddate": 1756886400
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evaluation list"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/pricingAi/payload/{evalid}": {
      "get": {
        "tags": [
          "Pricing AI"
        ],
        "summary": "Pricing AI payload for an evaluation",
        "description": "Wholesale / retail range for a **Complete** eval that has mileage. Pass `reasoning=1` to request dealer- and customer-facing insight bullets. Reasoning is async (10–90s): while `reasoning.status` is `processing`, `dealer` and `customer` are null — poll the same URL every 10–15 seconds for up to ~3 minutes until `completed` or `unavailable`.",
        "operationId": "pricingAiPayload",
        "parameters": [
          {
            "name": "evalid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "reasoning",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "Pass 1/true to include the reasoning block. Not returned unless requested."
          },
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ],
        "responses": {
          "200": {
            "description": "Pricing payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricingAiPayload"
                },
                "example": {
                  "confidence": "Medium",
                  "prediction_date": "2026-03-30",
                  "prices": {
                    "wholesale": 49500,
                    "wholesale_low": 49500,
                    "wholesale_high": 51500,
                    "buy_to_retail": 48150,
                    "retail": 63500,
                    "quick_sale": 58500,
                    "sweet_spot": 63500
                  },
                  "currency_symbol": "$",
                  "evalId": 123456
                }
              }
            }
          },
          "400": {
            "description": "Evaluation not found"
          },
          "403": {
            "description": "Forbidden or invalid API key"
          },
          "429": {
            "description": "Sandbox real-lookup quota reached"
          }
        }
      }
    },
    "/getuserlist": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "List salesperson names and emails",
        "description": "Users in the API key's dealer group (all rooftops, not only the key's dealership), ordered by name. Use `Email` as `userEmail` on `/dcupdateeval`. Eval Link generated accounts are never returned. Only active users by default.",
        "operationId": "getUserList",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user": {
                    "type": "string",
                    "description": "Optional. Single user by email or UserID. Must be in the same dealer group."
                  },
                  "dealerId": {
                    "type": "string",
                    "description": "Optional. JSON array of dealership ids from /getevallist DealerID, e.g. \"[1234,1235]\"."
                  },
                  "includeInactive": {
                    "type": "boolean",
                    "default": false,
                    "description": "Include deactivated users (staff who have left)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "User list (empty array if none match)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "Name": {
                        "type": "string"
                      },
                      "Email": {
                        "type": "string"
                      }
                    }
                  }
                },
                "example": [
                  {
                    "Name": "Jane Smith",
                    "Email": "jane.smith@exampledealer.com.au"
                  },
                  {
                    "Name": "Mark Taylor",
                    "Email": "mark.taylor@exampledealer.com.au"
                  }
                ]
              }
            }
          },
          "403": {
            "description": "Forbidden or invalid API key"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/ppsrsearch": {
      "post": {
        "tags": [
          "Vehicle Checks"
        ],
        "summary": "PPSR search for an evaluation",
        "description": "AU keys only. Runs a PPSR search on the eval's VIN and attaches the certificate. `evalid` is the only parameter — no VIN on the request. The eval does not need to be Complete, but it must have a real 17-character VIN. Generation is synchronous; allow 60s+ client timeout.",
        "operationId": "ppsrSearch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  }
                }
              },
              "example": {
                "evalid": 123456
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Certificate attached",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PpsrCertificate"
                }
              }
            }
          },
          "400": {
            "description": "evalid missing, eval has no VIN, search failed, or daily per-vehicle limit"
          },
          "403": {
            "description": "Not an AU account, or invalid key"
          },
          "404": {
            "description": "Evaluation not found"
          },
          "429": {
            "description": "Daily throttle"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/carhistory": {
      "post": {
        "tags": [
          "Vehicle Checks"
        ],
        "summary": "Car History report for an evaluation",
        "description": "AU keys only. Dealership must have Car History enabled (otherwise 403). Same rules as `/ppsrsearch`: `evalid` only, VIN taken from the eval. Only one report can run for the same eval at a time (429 `Request in progress, please wait`). Does not include SearchNumber / CertificateNumber or safety-recall data — use `/getpastcarhistory` for the stored transaction reference.",
        "operationId": "carHistory",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Report attached"
          },
          "400": {
            "description": "evalid missing or eval has no VIN"
          },
          "403": {
            "description": "Not enabled for the dealership, not an AU account, or invalid key"
          },
          "404": {
            "description": "Evaluation not found"
          },
          "429": {
            "description": "Daily throttle, or a report for this eval is already running"
          },
          "500": {
            "description": "Failed to get vehicle history"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/getpastppsrsearch": {
      "post": {
        "tags": [
          "Vehicle Checks"
        ],
        "summary": "Past PPSR certificates for an evaluation",
        "description": "AU keys only. Newest first, with freshly signed PDF links (valid 1 week). Also includes certificates generated against other evals in the same dealer group for the same VIN. Empty array when none exist.",
        "operationId": "getPastPpsrSearch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Array of past certificates including WatchStatus"
          },
          "400": {
            "description": "evalid missing or eval has no VIN"
          },
          "403": {
            "description": "Not an AU account or invalid key"
          },
          "404": {
            "description": "Evaluation not found"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/getpastcarhistory": {
      "post": {
        "tags": [
          "Vehicle Checks"
        ],
        "summary": "Past Car History reports for an evaluation",
        "description": "AU keys only. Same shape as `/getpastppsrsearch`. `SearchNumber` is the provider transaction reference. `CertificateNumber` is the PPSR certificate when one was also retrieved, otherwise null.",
        "operationId": "getPastCarHistory",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Array of past reports including WatchStatus"
          },
          "400": {
            "description": "evalid missing or eval has no VIN"
          },
          "403": {
            "description": "Not an AU account or invalid key"
          },
          "404": {
            "description": "Evaluation not found"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/ppsrwatch": {
      "post": {
        "tags": [
          "Vehicle Checks"
        ],
        "summary": "PPSR watch status for an evaluation",
        "description": "AU keys only. Reports whether security-interest details changed since a PPSR / Car History run, without paying for a new search. PPSR monitors each completed search for 14 days. Free to poll.\n\nTop-level `WatchStatus` is the most noteworthy across all checks, in order: `changed`, `expired`, `watching`, `unchanged`, `disabled`, `not_watched`. Written-off / stolen changes are **not** reported — run a new search. Once a check is `changed` or `expired` it does not change again; run `/ppsrsearch` to start a fresh 14-day watch.",
        "operationId": "ppsrWatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Watch summary plus Checks[] (empty and not_watched when no successful search exists yet)"
          },
          "400": {
            "description": "evalid missing or eval has no VIN"
          },
          "403": {
            "description": "Not an AU account or invalid key"
          },
          "404": {
            "description": "Evaluation not found"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/partner/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List evaluation webhooks",
        "description": "List eval and drive webhook subscriptions for the rooftop on this partner key.",
        "operationId": "listPartnerWebhooks",
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook list"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Create an evaluation webhook",
        "description": "Register an HTTPS POST receiver. Provide the URL, optional custom auth headers (`auth_header_name` / `auth_header_value`), and a trigger.\n\nThe delivered body is the `/geteval` payload (see Evaluation schema).\n\n**Self-serve triggers:** `ON_COMPLETE` (eval finished), `ON_EXPORT` (Export pressed in the UI). Drive type: `ON_DRIVE_COMPLETED`.\n\n`ON_SHARE_OFFER` (offer/price added on a shared eval) and `ON_EXTERNAL_PRICE` (custom/manual user-pricing add or update) exist but are not self-serve on this partner CRUD — ask AlgoDriven if you need them.",
        "operationId": "createPartnerWebhook",
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "trigger_event",
                  "webhook"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "eval",
                      "drive"
                    ]
                  },
                  "trigger_event": {
                    "type": "string",
                    "enum": [
                      "ON_COMPLETE",
                      "ON_EXPORT",
                      "ON_DRIVE_COMPLETED"
                    ]
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri",
                    "description": "HTTPS POST URL"
                  },
                  "auth_header_name": {
                    "type": "string"
                  },
                  "auth_header_value": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "type": "eval",
                "trigger_event": "ON_COMPLETE",
                "webhook": "https://example.com/eval-complete",
                "auth_header_name": "Authorization",
                "auth_header_value": "Bearer xyz"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created"
          }
        }
      }
    },
    "/partner/webhooks/{id}": {
      "put": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Update an evaluation webhook",
        "operationId": "updatePartnerWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "eval",
                      "drive"
                    ]
                  },
                  "trigger_event": {
                    "type": "string"
                  },
                  "webhook": {
                    "type": "string",
                    "format": "uri"
                  },
                  "auth_header_name": {
                    "type": "string"
                  },
                  "auth_header_value": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook updated"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete an evaluation webhook",
        "operationId": "deletePartnerWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook deleted"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/imagelist": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "List an evaluation's photos",
        "description": "The photos on an evaluation, in display order. Use it to get the image ids that `/imageorder` and `/imagehero` take.\n\nWorks at any stage — unlike `/geteval`, the evaluation does not need to be Complete, so this is the endpoint to call while photos are still being captured.\n\nA POST because it reads like `/geteval` does, not because it changes anything.",
        "operationId": "imageList",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer",
                    "description": "EvalID from /entervin"
                  }
                }
              },
              "example": {
                "evalid": 123456
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Photo list in display order. `Photos` is an empty array when the evaluation has no photos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "Photos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Photo"
                      }
                    }
                  }
                },
                "example": {
                  "Photos": [
                    {
                      "id": 23942096,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 1,
                      "hero": true,
                      "heroSource": "default",
                      "angle": "exterior_front_three_quarter"
                    },
                    {
                      "id": 23942097,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 2,
                      "hero": false,
                      "heroSource": null,
                      "angle": "interior_odometer"
                    },
                    {
                      "id": 23942098,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942098.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": null,
                      "hero": false,
                      "heroSource": null,
                      "angle": null
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key may not access this evaluation, or the evaluation does not exist"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/imageorder": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Reorder evaluation photos",
        "description": "Sets gallery display order. Get the image ids from `/imagelist` (or from the `/imageupload` response).\n\nReordering **does not move the hero** — pass `hero` here, or call `/imagehero`, to change it. Ids you leave out of `ids` keep their relative order after the listed ones, so a partial list is a safe way to promote a few photos.\n\nThe returned `Photos` array is in the new display order.",
        "operationId": "imageOrder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid",
                  "ids"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "Image ids in the desired display order. Need not list every photo. Also accepted as a comma-separated string (`\"41,39,40\"`). Ids belonging to another evaluation are ignored."
                  },
                  "hero": {
                    "type": "integer",
                    "description": "Optional. Image id to pin as the hero in the same call, applied after the reorder. Saves a second request to /imagehero. Must be an image on this evaluation."
                  }
                }
              },
              "example": {
                "evalid": 123456,
                "ids": [
                  23942097,
                  23942096
                ],
                "hero": 23942097
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated photo list, in the new display order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "Photos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Photo"
                      }
                    }
                  }
                },
                "example": {
                  "success": true,
                  "Photos": [
                    {
                      "id": 23942097,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 1,
                      "hero": true,
                      "heroSource": "manual",
                      "angle": "exterior_front"
                    },
                    {
                      "id": 23942096,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 2,
                      "hero": false,
                      "heroSource": null,
                      "angle": "exterior_front_three_quarter"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`ids` is missing, empty, or contains a non-numeric value"
          },
          "403": {
            "description": "The key may not access this evaluation, or the evaluation does not exist. A missing or non-positive `evalid` also answers 403, not 400."
          },
          "404": {
            "description": "`hero` is not an image on this evaluation. The reorder has already been applied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "hero is not an image on this evaluation"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    },
    "/imagehero": {
      "post": {
        "tags": [
          "Evaluations"
        ],
        "summary": "Set or clear the hero photo",
        "description": "Pins an image as the hero. The hero is independent of gallery order, so pinning one does not move it in the gallery and reordering the gallery does not unpin it.\n\nA pin is permanent until you change it. Pass `imgid: null` to remove it and fall back to the default, which is the first photo in display order.\n\nThe pinned photo comes back with `hero: true` and `heroSource: \"manual\"`; after clearing, the first photo comes back with `heroSource: \"default\"`.",
        "operationId": "imageHero",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "evalid"
                ],
                "properties": {
                  "evalid": {
                    "type": "integer"
                  },
                  "imgid": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Image id to pin, or null to clear the pin and fall back to the first photo in display order"
                  }
                }
              },
              "example": {
                "evalid": 123456,
                "imgid": 23942096
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated photo list, in display order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "Photos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Photo"
                      }
                    }
                  }
                },
                "example": {
                  "success": true,
                  "Photos": [
                    {
                      "id": 23942097,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 1,
                      "hero": false,
                      "heroSource": null,
                      "angle": "exterior_front"
                    },
                    {
                      "id": 23942096,
                      "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                      "order": 2,
                      "hero": true,
                      "heroSource": "manual",
                      "angle": "exterior_front_three_quarter"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "The key may not access this evaluation, or the evaluation does not exist. A missing or non-positive `evalid` also answers 403, not 400."
          },
          "404": {
            "description": "`imgid` is not an image on this evaluation. Nothing was changed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "imgid is not an image on this evaluation"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XDealerId"
          }
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "XDealerId": {
        "name": "X-Dealer-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Rooftop to act as. Value is the dealer's `uid` (not the numeric DealerID). Required on a multi-rooftop partner key; ignored when the key is already bound to a single dealer."
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "Partner API key issued by AlgoDriven. Send the raw key (not Bearer)."
      }
    },
    "schemas": {
      "Evaluation": {
        "type": "object",
        "description": "/geteval row and webhook body. Array of one or more of these objects.",
        "properties": {
          "ID": {
            "type": "string",
            "description": "Evaluation unique ID"
          },
          "EntryDate": {
            "type": "string",
            "description": "Legacy. Creation time rendered in the dealer's local timezone with no zone designator (YYYY-MM-dd hh:mm:ss). Use CreatedAt instead."
          },
          "CreatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the evaluation was created. ISO-8601 UTC, e.g. 2026-09-04T22:40:29Z."
          },
          "CompletedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the evaluation was first marked Complete. ISO-8601 UTC. Null while in progress."
          },
          "UpdatedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Last write to the evaluation row. ISO-8601 UTC."
          },
          "UserName": {
            "type": "string",
            "description": "Salesperson name. Eval Link captures begin \"Eval Link -\""
          },
          "UserEmail": {
            "type": "string",
            "description": "Salesperson email. Eval Link captures end .evallink@algodriven.xyz (not a mailbox)."
          },
          "VIN": {
            "type": "string"
          },
          "EngineNo": {
            "type": "string"
          },
          "KM": {
            "type": "string"
          },
          "Description": {
            "type": "string"
          },
          "Make": {
            "type": "string"
          },
          "Model": {
            "type": "string"
          },
          "Variant": {
            "type": "string"
          },
          "Series": {
            "type": "string"
          },
          "MY": {
            "type": "string"
          },
          "BuildYr": {
            "type": "string"
          },
          "BuildMth": {
            "type": "string"
          },
          "CompYr": {
            "type": "string"
          },
          "CompMth": {
            "type": "string"
          },
          "Body": {
            "type": "string"
          },
          "Doors": {
            "type": "string"
          },
          "Seats": {
            "type": "string"
          },
          "Fuel": {
            "type": "string"
          },
          "Cylinders": {
            "type": "string"
          },
          "Litres": {
            "type": "string"
          },
          "Drive": {
            "type": "string"
          },
          "Transmission": {
            "type": "string"
          },
          "Ext_Colour": {
            "type": "string"
          },
          "Int_Colour": {
            "type": "string"
          },
          "Registration": {
            "type": "string"
          },
          "RegExpiry": {
            "type": "string"
          },
          "SpareKey": {
            "type": "string",
            "description": "1 spare key, 0 none"
          },
          "ServiceHistory": {
            "type": "string",
            "description": "5 Full Genuine, 4 Full Non-Genuine, 3 Partial, 2 Missing book, 1/0 legacy, null unknown"
          },
          "Condition": {
            "description": "Integer 0–10, or null"
          },
          "Reconditioning": {
            "description": "Currency amount of recon required. Line items, including Tesla battery SoH, are reconditioning_list."
          },
          "reconditioning_list": {
            "type": "array",
            "description": "Same line items as the evaluation details page. Cost rows are money. type \"info\" is a reading: Tesla battery SoH has source \"tesla\", cost 0, value such as \"93.3%\", and a label that includes the reading date.",
            "items": {
              "$ref": "#/components/schemas/ReconditioningItem"
            }
          },
          "Traded": {
            "description": "null unknown, 0 not traded, 1 traded"
          },
          "Status": {
            "type": "string",
            "description": "TRADED, PURCHASED, PENDING, LOST_SOLD_ELSEWHERE, LOST_CUSTOMER_KEEPING, LOST_NOT_PURCHASING_FROM_US, LOST_NOT_BUYING_ANYMORE, or null"
          },
          "Notes": {
            "type": "string"
          },
          "CarPics": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "FinalPrice": {
            "description": "Appraisal amount from /dcupdateeval price"
          },
          "Ccy": {
            "type": "string"
          },
          "URL": {
            "type": "string",
            "format": "uri",
            "description": "Open the eval in EvalExpert"
          },
          "UserPrices": {
            "type": "array",
            "description": "Offer / guide history. Empty array when none exist. Not written by setting price on /dcupdateeval.",
            "items": {
              "$ref": "#/components/schemas/UserPrice"
            }
          },
          "Photos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Photo"
            },
            "description": "Per-photo detail (id, order, hero, angle), in display order. `CarPics` remains an ordered array of URL strings for compatibility and is the same photos in the same order. Use `Photos[].hero` to pick the main image; use `/imagelist` to read photos on an evaluation that is not yet Complete."
          }
        },
        "example": [
          {
            "ID": "36392",
            "EntryDate": "2018-02-05 20:33:20",
            "CreatedAt": "2018-02-06T01:33:20Z",
            "CompletedAt": "2018-02-06T01:41:07Z",
            "UpdatedAt": "2018-02-06T01:41:07Z",
            "UserName": "John Doe",
            "UserEmail": "john.doe@exampledealer.com.au",
            "VIN": "WVWZZZ1KZCP079822",
            "KM": "28000",
            "Description": "2011 Volkswagen Golf VI 90TSI Trendline Hatchback 5dr DSG 7sp 1.4T [MY12.5]",
            "Make": "Volkswagen",
            "Model": "Golf",
            "Variant": "90TSI Trendline",
            "Condition": null,
            "Reconditioning": "0",
            "reconditioning_list": [
              {
                "cost": 0,
                "label": "Battery Health SoH 21/09/26: 93.3%",
                "type": "info",
                "source": "tesla",
                "value": "93.3%",
                "measured_at": "2026-09-21 12:11:05"
              }
            ],
            "Notes": "Example of notes on evaluation.",
            "CarPics": [
              "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
              "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE"
            ],
            "Photos": [
              {
                "id": 23942096,
                "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942096.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                "order": 1,
                "hero": true,
                "heroSource": "default",
                "angle": "exterior_front_three_quarter"
              },
              {
                "id": 23942097,
                "url": "https://d2tw3ay4mzcls3.cloudfront.net/img/23942097.jpg?Expires=1913200000&Signature=EXAMPLE&Key-Pair-Id=EXAMPLE",
                "order": 2,
                "hero": false,
                "heroSource": null,
                "angle": "interior_odometer"
              }
            ],
            "FinalPrice": null,
            "Ccy": "$",
            "URL": "https://app.evalexpert.io/web/evaluations.php?evalid=301041",
            "UserPrices": [
              {
                "Name": "Trade-in guide (23-07-2026)",
                "Price": "$35,650 - $40,000",
                "priceType": "guide",
                "LogTime": "2026-07-23 04:04:19"
              },
              {
                "Name": "John Doe (23-07-2026)",
                "Price": "29000",
                "priceType": "share",
                "LogTime": "2026-07-23 05:45:00"
              }
            ]
          }
        ]
      },
      "ReconditioningItem": {
        "type": "object",
        "properties": {
          "cost": {
            "description": "Dollar amount, or 0 for a reading such as Tesla battery SoH."
          },
          "label": {
            "type": "string",
            "description": "Line title. Tesla SoH includes the reading date, e.g. \"Battery Health SoH 21/09/26: 93.3%\"."
          },
          "type": {
            "type": "string",
            "description": "repair, replace, or info. info is a reading, not a cost."
          },
          "source": {
            "type": "string",
            "description": "recon, autohub, damage_ai, or tesla."
          },
          "value": {
            "type": "string",
            "description": "Reading for type info, e.g. 93.3%. Absent on cost rows."
          },
          "measured_at": {
            "type": "string",
            "description": "UTC clock of the reading, YYYY-MM-dd hh:mm:ss, no zone designator. Absent on cost rows."
          }
        }
      },
      "UserPrice": {
        "type": "object",
        "properties": {
          "Name": {
            "type": "string"
          },
          "Price": {
            "type": "string"
          },
          "priceType": {
            "type": "string",
            "enum": [
              "guide",
              "share"
            ]
          },
          "LogTime": {
            "type": "string",
            "description": "UTC, YYYY-MM-dd hh:mm:ss, no zone designator"
          }
        }
      },
      "PricingAiPayload": {
        "type": "object",
        "properties": {
          "confidence": {
            "type": "string"
          },
          "prediction_date": {
            "type": "string"
          },
          "prices": {
            "type": "object",
            "properties": {
              "wholesale": {
                "type": "integer"
              },
              "wholesale_low": {
                "type": "integer"
              },
              "wholesale_high": {
                "type": "integer"
              },
              "buy_to_retail": {
                "type": "integer"
              },
              "retail": {
                "type": "integer"
              },
              "quick_sale": {
                "type": "integer"
              },
              "sweet_spot": {
                "type": "integer"
              }
            }
          },
          "currency_symbol": {
            "type": "string"
          },
          "evalId": {
            "type": "integer"
          },
          "reasoning": {
            "type": "object",
            "description": "Only present when reasoning=1.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "processing",
                  "completed",
                  "unavailable"
                ]
              },
              "dealer": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "nullable": true
              },
              "customer": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "nullable": true
              }
            }
          }
        }
      },
      "PpsrCertificate": {
        "type": "object",
        "properties": {
          "Filename": {
            "type": "string"
          },
          "Status": {
            "type": "boolean"
          },
          "SearchNumber": {
            "type": "string"
          },
          "CertificateNumber": {
            "type": "string"
          },
          "URL": {
            "type": "string"
          },
          "Interests": {
            "type": "integer"
          },
          "WOV": {
            "type": "integer"
          },
          "Stolen": {
            "type": "integer"
          },
          "VIN": {
            "type": "string"
          },
          "EvalID": {
            "type": "integer"
          },
          "encumbranceDetails": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "WrittenOffDetails": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "VehicleDescription": {
            "type": "object"
          },
          "VehicleRegistration": {
            "type": "object"
          }
        }
      },
      "Photo": {
        "type": "object",
        "description": "One evaluation photo.\n\n**Photo arrays are always returned in display order** — rely on the array, not on `order`. Reconditioning photos are never included and can never be the hero.\n\n**Do not assume exactly one photo has `hero: true`.** On an evaluation shared between dealers the pinned photo can sit outside the visible set, and rather than mislabel another photo the whole array comes back with `hero: false`. Treat \"no hero\" as a normal outcome and fall back to the first photo.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Pass this as an entry in `ids` on /imageorder, or as `imgid` on /imagehero."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Signed CloudFront URL for the image (path under `/img/`, ~1-year expiry). Re-fetch via `/imagelist` or `/geteval` before the URL expires if you persist it — do not assume a permanent public URL."
          },
          "order": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Display position, 1-based, advisory only. Null when the photo has never been given one — a partial /imageorder leaves unlisted photos null, and null sorts *last*, not first. Values are not unique, not contiguous, and grow on each partial reorder, so treat them as opaque and read position from the array instead."
          },
          "hero": {
            "type": "boolean",
            "description": "Whether this is the evaluation's hero (main) photo. At most one photo in the array has it, and none may."
          },
          "heroSource": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why this photo is the hero. Null on every photo that is not the hero.\n- `manual` — pinned via /imagehero, `hero` on /imageorder, or `hero: 1` on /imageupload. Permanent until changed.\n- `default` — nothing is pinned, so it is simply the first photo in display order.\n- `auto` — reserved for angle-based selection, which is not enabled. No response returns it today.",
            "enum": [
              "manual",
              "auto",
              "default",
              null
            ]
          },
          "angle": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detected camera angle, informational. Null until classification has run, so it is normally null immediately after upload and populated on a later read — do not block on it. `exterior_side_driver` / `exterior_side_passenger` are resolved for the dealer's market, so the driver's side is the right-hand side on an AU or GCC evaluation and the left on a US one.",
            "enum": [
              "exterior_front_three_quarter",
              "exterior_front",
              "exterior_side_driver",
              "exterior_rear_three_quarter",
              "exterior_rear",
              "exterior_side_passenger",
              "exterior_roof",
              "exterior_wheel",
              "exterior_detail",
              "interior_dashboard",
              "interior_steering",
              "interior_odometer",
              "interior_infotainment",
              "interior_front_seats",
              "interior_rear_seats",
              "interior_console",
              "interior_detail",
              "trunk_cargo",
              "trunk_spare",
              "engine_bay",
              "engine_detail",
              "documents_service_book",
              "documents_registration",
              "documents_keys",
              "documents_other",
              "other",
              null
            ]
          }
        },
        "required": [
          "id",
          "url",
          "hero"
        ]
      }
    }
  },
  "x-sandbox": {
    "au": {
      "test_vins": [
        "6G1EP5EH6AL460983",
        "1C6RR7FT6MS537299",
        "MMAJLKL10PH006556",
        "5YJSB7E24JF257977"
      ],
      "test_regos": [
        "1DLE148",
        "EOB14K",
        "RG595",
        "CGZ017"
      ],
      "hint": "These VINs and regos return canned vehicle data from /entervin and never reach a paid decoder, so they do not consume real_lookups. Any other VIN or rego consumes one on its first decode and is free on every repeat."
    },
    "us": {
      "test_vins": [
        "1FTFW1EF2HFB45917",
        "JF1VBAF62P9812865",
        "1C4RJFBG9KC749141"
      ],
      "test_vins_detail": {
        "1FTFW1EF2HFB45917": "2017 Ford F-150 XLT SuperCrew 4x4, 5.0L V8 (Chrome style 387934)",
        "JF1VBAF62P9812865": "2023 Subaru WRX Premium, 2.4L turbo, 6-speed manual (Chrome style 435451)",
        "1C4RJFBG9KC749141": "2019 Jeep Grand Cherokee Limited 4x4, 3.6L V6 (Chrome style 400116)"
      },
      "hint": "Real North American VINs taken from US dealer decode logs, so each passes the VIN check-digit test and decodes to exactly one vehicle. Each has a sample evaluation already seeded and its Black Book and Chrome Data decodes already cached, so working through them costs no real_lookups. Any other VIN consumes one on its first decode and is free on every repeat, so you can create as many evaluations from these as you like. Do not use Australian sample VINs (1C6RR7FT6MS537299, MMAJLKL10PH006556, 6G1EP5EH6AL460983, 5YJSB7E24JF257977) on a US key — they are AU-market vehicles, some do not carry a valid check digit, and on a US key they will either fail to decode or return AU data.",
      "needing_clarification": [
        "4T1BZ1HK4KU025338",
        "SALVP2BG6HH236343"
      ],
      "needing_clarification_detail": {
        "4T1BZ1HK4KU025338": "2019 Toyota Camry — two styles: XSE V6 (code 401553) or XLE V6 (code 401555), both codeType chrome",
        "SALVP2BG6HH236343": "2017 Land Rover Range Rover Evoque — two styles: SE (code 384827) or SE Premium (code 384828), both codeType chrome"
      },
      "clarification_workflow": {
        "why": "A US VIN does not always identify one vehicle. Where a model year shares a drivetrain across trims, Chrome Data returns several candidate styles and the evaluation cannot be completed until one is chosen. Both VINs above are real US dealer decodes that end in a two-way trim choice.",
        "step_1": "POST /entervin with the VIN. Instead of the usual evaluation array the response is a single object: { VIN, EvalID, descriptionSelect: true, descriptionOptions: [ { code, codeType, fullDescription, highlight } ] }. Detect this case by `descriptionSelect === true` — not by a null EvalID. On US keys the evaluation is created before the options are returned, so EvalID is always populated here; keep it.",
        "step_2": "POST /manualcarentry with { vin, evalid, code, codeType } where evalid is the EvalID from step 1, code is the chosen option's code and codeType is that same option's codeType. Echo the option back rather than hardcoding a provider: the list is normally built from Chrome Data but can come from Black Book, and both are accepted. The response is the same decoded-vehicle array /entervin returns on a single match, carrying the same EvalID.",
        "note": "Choosing a style is recorded against that evaluation only, so two evaluations of one VIN can hold different styles. Neither step costs a real_lookup for this VIN: its decode is cached, and creating an evaluation is never metered. Work through the exchange as often as you need."
      }
    },
    "rate_limits_sandbox": {
      "daily": 50,
      "monthly": 500,
      "real_lookups": 20,
      "real_lookups_note": "Counts distinct VINs you decode and Pricing AI predictions, not requests. Repeating a VIN, creating evaluations, and reading evaluations are all unlimited, so the fixture VINs above never draw it down. This is a lifetime total for the key, not a daily or monthly one, so it does not reset on its own — ask your AlgoDriven contact if you need it cleared. Exceeding it returns 429."
    },
    "webhook_signature": {
      "header": "X-AlgoDriven-Signature",
      "format": "sha256=<hex>",
      "signed_payload": "{timestamp}.{raw_json_body}",
      "timestamp_header": "X-AlgoDriven-Timestamp"
    }
  }
}
