{"openapi":"3.0.3","info":{"title":"LaneScan API","version":"1.0.0","description":"Pull road-marking audits, segments, quotes and reports into your own systems, and create quotes and jobs. Every request is scoped to the organisation that owns the API key."},"servers":[{"url":"/api/public"}],"security":[{"ApiKey":[]}],"components":{"securitySchemes":{"ApiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Created by an admin in Settings → API. Shown once."}},"responses":{"Error":{"description":"Error","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}}}}},"schemas":{"Audit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"road_name":{"type":"string","nullable":true},"status":{"type":"string"},"purpose":{"type":"string","nullable":true},"started_at":{"type":"string","format":"date-time"},"ended_at":{"type":"string","format":"date-time","nullable":true},"distance_m":{"type":"number"},"summary":{"type":"object","nullable":true,"description":"Present once segments are built (also in the list): total_km, km_by_class, pct_poor_or_missing, avg_score (length-weighted), segments"}}},"Quote":{"type":"object","properties":{"id":{"type":"string"},"number":{"type":"string"},"status":{"type":"string","enum":["draft","sent","accepted","rejected"]},"currency":{"type":"string"},"subtotal":{"type":"number"},"tax":{"type":"number"},"total":{"type":"number"},"painted_m":{"type":"number","description":"Total metres of line to paint (sum of the metre lines)"},"road_length_m":{"type":"number","description":"Length of road covered by the quoted segments"},"segment_count":{"type":"integer"},"crew_days":{"type":"integer","nullable":true},"lines":{"type":"array","items":{"type":"object"}}}},"Job":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","enum":["planned","in_progress","done","verified"]},"external_ref":{"type":"string"},"quote_id":{"type":"string"},"before_audit_id":{"type":"string","nullable":true},"after_audit_id":{"type":"string","nullable":true}}},"WebhookPayload":{"type":"object","description":"POSTed to your URL. Header x-linescan-signature: sha256=<hex HMAC-SHA256 of the raw body using your webhook secret>.","properties":{"event":{"type":"string","enum":["audit.completed","review.completed","quote.created","verification.completed","test"]},"id":{"type":"string","description":"Event id — use it to ignore duplicates"},"organization_id":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"},"data":{"type":"object"}}}}},"paths":{"/v1/audits":{"get":{"summary":"List audits","parameters":[{"name":"status","in":"query","schema":{"type":"string"},"description":"Comma-separated, e.g. complete,review"},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Up to 500 audits, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Audit"}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/audits/{id}":{"get":{"summary":"Get an audit with its summary","parameters":[{"name":"id","in":"path","required":true,"description":"Audit id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Audit"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/audits/{id}/segments":{"get":{"summary":"Audit segments as GeoJSON","parameters":[{"name":"id","in":"path","required":true,"description":"Audit id (UUID)","schema":{"type":"string","format":"uuid"}},{"name":"class","in":"query","schema":{"type":"string"},"description":"Comma-separated classes: good,fair,poor,missing,needs_review,unpaved,unusable"}],"responses":{"200":{"description":"GeoJSON FeatureCollection of LineStrings"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/segments/{id}":{"get":{"summary":"Get a segment with frame image URLs (valid 10 minutes)","parameters":[{"name":"id","in":"path","required":true,"description":"Segment id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Segment"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/quotes":{"post":{"summary":"Create a draft quote from segments (needs a read + write key)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["segment_ids"],"properties":{"segment_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"client_name":{"type":"string"}}}}}},"responses":{"201":{"description":"Quote","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Quote"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/quotes/{id}":{"get":{"summary":"Get a quote with its lines","parameters":[{"name":"id","in":"path","required":true,"description":"Quote id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Quote"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/jobs":{"post":{"summary":"Create a job from a quote (needs a read + write key)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_id","external_ref"],"properties":{"quote_id":{"type":"string","format":"uuid"},"external_ref":{"type":"string","description":"Your system's job id"},"name":{"type":"string"}}}}}},"responses":{"201":{"description":"Job","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Job"}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/jobs/{id}/verify":{"post":{"summary":"Create the verification audit shell (needs a read + write key)","parameters":[{"name":"id","in":"path","required":true,"description":"Job id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"{ job_id, audit_id }"},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"403":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/jobs/{id}/certificate":{"get":{"summary":"Signed completion-certificate PDF URL (valid 10 minutes). 404 not_generated until an admin or reviewer generates it in LaneScan.","parameters":[{"name":"id","in":"path","required":true,"description":"Job id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ job_id, generated_at, url, expires_in }"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/v1/reports/{id}/download":{"get":{"summary":"Signed PDF download URL (valid 10 minutes)","parameters":[{"name":"id","in":"path","required":true,"description":"Report id (UUID)","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"{ url, expires_in }"},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}}}}