{"openapi":"3.0.3","info":{"title":"ClearHandoff API","version":"1.0","description":"Public REST API for uploading drawings, triggering AI reviews, and retrieving results.\n\nThis is the endpoint reference; for a high-level overview, see the [API help page](https://www.clearhandoff.com/en/help/api)."},"paths":{"/drawings/":{"get":{"operationId":"list_drawings","description":"List all drawings belonging to the API key's team, ordered by last updated.","summary":"List drawings","parameters":[{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of drawings to return (max 100, default 20)."},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Number of drawings to skip before returning results (default 0). Follow the `next` and `previous` URLs in the response instead of computing offsets manually."},{"in":"query","name":"search","schema":{"type":"string"},"description":"Filter by drawing name (case-insensitive substring match)."}],"tags":["Drawings"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedDrawingList"},"examples":{"TwoDrawingsOnTheTeam":{"value":{"count":2,"next":null,"previous":null,"results":[{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","version_count":2,"latest_version":{"version_number":2,"page_count":3,"pdf_status":"completed","created_at":"2026-04-20T09:00:00.000000Z"},"approval_status":"changes_requested","created_at":"2026-04-18T14:22:10.000000Z","updated_at":"2026-04-20T09:05:15.123456Z"},{"id":"c8e441a3-9b2e-4d7f-a1c2-5b3d9e8f7a6b","name":"panel-assembly","url":"https://clearhandoff.com/drawing/c8e441a3-9b2e-4d7f-a1c2-5b3d9e8f7a6b","version_count":1,"latest_version":{"version_number":1,"page_count":1,"pdf_status":"completed","created_at":"2026-04-19T16:41:02.000000Z"},"approval_status":null,"created_at":"2026-04-19T16:41:02.000000Z","updated_at":"2026-04-19T16:41:02.000000Z"}]},"summary":"Paginated list of drawings with mixed approval status"}}}},"description":""}}},"post":{"operationId":"create_drawing","description":"Upload a PDF or DXF to create a new drawing with its first version. PDFs are ready immediately (`pdf_status` \"completed\"). DXFs are converted to PDF asynchronously (`pdf_status` \"pending\"): an AI review may be triggered right away — it runs once the conversion completes — and only downloading the PDF requires waiting for \"completed\".\n\nPass `trigger_ai_review=true` to upload and start the AI review in one call (one AI credit; the response carries `review_id` to poll). Atomic: if the review cannot be created — e.g. no credits (402) — nothing is stored.","summary":"Create a new drawing","tags":["Drawings"],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1CreateDrawingRequest"}}},"required":true},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UploadResponse"},"examples":{"DrawingCreated":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","version_number":1,"page_count":3,"pdf_status":"completed","review_id":null,"created_at":"2026-04-18T14:22:10.000000Z"},"summary":"New drawing with its first version (PDF upload)"},"DXFCreated,Converting":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","version_number":1,"page_count":null,"pdf_status":"pending","review_id":null,"created_at":"2026-04-18T14:22:10.000000Z"},"summary":"DXF upload — the PDF is derived asynchronously"},"UploadWithAIReview":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","version_number":1,"page_count":null,"pdf_status":"pending","review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","created_at":"2026-04-18T14:22:10.000000Z"},"summary":"trigger_ai_review=true — upload and review in one call"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Invalid upload; distinctive codes: \"missing_file\", \"invalid_file_type\", \"invalid_pdf\", \"trigger_ai_review_required\" (review parameters without trigger_ai_review=true)."},"401":{"description":"Invalid or missing API key."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Only with trigger_ai_review=true: no AI credit available — the upload is rejected and nothing is stored. Codes: \"trial_expired\", \"no_plan\", \"upgrade_required\", \"blocked\", \"cap_reached\"."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"File exceeds the size limit (20 MB for PDF, 5 MB for DXF); error_code \"file_too_large\"."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Only with trigger_ai_review=true: AI review service unavailable — retry later; error_code \"celery_unavailable\"."}}}},"/drawings/{drawing_id}/":{"get":{"operationId":"get_drawing","description":"Get full detail for a drawing including versions and latest AI review results.","summary":"Get drawing detail","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Drawings"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DrawingDetail"},"examples":{"DrawingWithTwoVersionsAndACompletedAIReview":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","versions":[{"version_number":1,"page_count":3,"pdf_status":"completed","created_at":"2026-04-18T14:22:10.000000Z"},{"version_number":2,"page_count":3,"pdf_status":"completed","created_at":"2026-04-20T09:00:00.000000Z"}],"latest_version":{"version_number":2,"page_count":3,"pdf_status":"completed","created_at":"2026-04-20T09:00:00.000000Z","original_pdf_url":"https://clearhandoff.com/api/v1/drawings/b5d6aa77-651f-458c-86a1-a3c333546828/pdf/","annotated_pdf_url":"https://clearhandoff.com/api/v1/drawings/b5d6aa77-651f-458c-86a1-a3c333546828/annotated-pdf/"},"latest_ai_review":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"completed","version_number":2,"pen":{"version":"1.1"},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":"2026-04-20T09:34:08.901521Z","error_message":"","findings":[{"page":2,"issue":"Missing tolerance on the 200 mm base plate dimension.","confidence":"High"}],"summary":"Drawing is mostly clear; one tolerance call-out is missing.","recommendation":"changes_requested"},"created_at":"2026-04-18T14:22:10.000000Z","updated_at":"2026-04-20T09:34:08.901521Z"},"summary":"Detail view showing versions, latest annotated PDF, and latest AI review"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}},"patch":{"operationId":"rename_drawing","description":"Update the name of a drawing.","summary":"Rename a drawing","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Drawings"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchedV1RenameDrawingRequest"},"examples":{"RenameRequest":{"value":{"name":"bracket-weldment rev C"},"summary":"New name for the drawing"}}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/PatchedV1RenameDrawingRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/PatchedV1RenameDrawingRequest"}}}},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1DrawingDetail"},"examples":{"DrawingRenamed":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment rev C","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","versions":[{"version_number":1,"page_count":3,"pdf_status":"completed","created_at":"2026-04-18T14:22:10.000000Z"},{"version_number":2,"page_count":3,"pdf_status":"completed","created_at":"2026-04-20T09:00:00.000000Z"}],"latest_version":{"version_number":2,"page_count":3,"pdf_status":"completed","created_at":"2026-04-20T09:00:00.000000Z","original_pdf_url":"https://clearhandoff.com/api/v1/drawings/b5d6aa77-651f-458c-86a1-a3c333546828/pdf/","annotated_pdf_url":"https://clearhandoff.com/api/v1/drawings/b5d6aa77-651f-458c-86a1-a3c333546828/annotated-pdf/"},"latest_ai_review":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"completed","version_number":2,"pen":{"version":"1.1"},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":"2026-04-20T09:34:08.901521Z","error_message":"","findings":[{"page":2,"issue":"Missing tolerance on the 200 mm base plate dimension.","confidence":"High"}],"summary":"Drawing is mostly clear; one tolerance call-out is missing.","recommendation":"changes_requested"},"created_at":"2026-04-18T14:22:10.000000Z","updated_at":"2026-04-20T13:12:45.000000Z"},"summary":"Full drawing detail returned with the updated name"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"NameIsEmpty":{"value":{"error":"Name is required."},"summary":"Missing or whitespace-only name"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}},"delete":{"operationId":"delete_drawing","description":"Permanently delete a drawing and all its versions, reviews, and comments. A successful delete returns `204 No Content` — the response body is empty; success is signalled by the status code alone.","summary":"Delete a drawing","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Drawings"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"204":{"description":"Drawing deleted. Response body is empty — success is confirmed by the status code."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"DrawingNotFound":{"value":{"error":"Not found.","error_code":"not_found"},"summary":"Drawing ID does not exist or does not belong to this team"}}}},"description":""}}}},"/drawings/{drawing_id}/versions/":{"post":{"operationId":"upload_version","description":"Upload a PDF or DXF as a new version of an existing drawing. PDFs are ready immediately (`pdf_status` \"completed\"); DXFs convert to PDF asynchronously (`pdf_status` \"pending\") — an AI review may be triggered right away, and only PDF downloads require \"completed\".\n\nPass `trigger_ai_review=true` to upload and start the AI review in one call (one AI credit; the response carries `review_id` to poll). Atomic: if the review cannot be created — e.g. no credits (402) — nothing is stored.","summary":"Upload a new version","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Drawings"],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1UploadVersionRequest"}}},"required":true},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1UploadResponse"},"examples":{"SecondVersionUploaded":{"value":{"id":"b5d6aa77-651f-458c-86a1-a3c333546828","name":"bracket-weldment","url":"https://clearhandoff.com/drawing/b5d6aa77-651f-458c-86a1-a3c333546828","version_number":2,"page_count":3,"pdf_status":"completed","review_id":null,"created_at":"2026-04-20T09:00:00.000000Z"},"summary":"New version appended to an existing drawing"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Invalid upload; distinctive codes: \"missing_file\", \"invalid_file_type\", \"invalid_pdf\", \"trigger_ai_review_required\" (review parameters without trigger_ai_review=true)."},"401":{"description":"Invalid or missing API key."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Only with trigger_ai_review=true: no AI credit available — the upload is rejected and nothing is stored. Codes: \"trial_expired\", \"no_plan\", \"upgrade_required\", \"blocked\", \"cap_reached\"."},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Team seat limit exceeded; error_code \"seat_limit_exceeded\"."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"File exceeds the size limit (20 MB for PDF, 5 MB for DXF); error_code \"file_too_large\"."},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Only with trigger_ai_review=true: AI review service unavailable — retry later; error_code \"celery_unavailable\"."}}}},"/drawings/{drawing_id}/approvals/":{"get":{"operationId":"list_approvals","description":"List all approval decisions (AI and human) for a drawing version. Defaults to the latest version. Use the `type` field to distinguish AI entries from human decisions. For AI review findings, use the /ai-review/ endpoints instead.","summary":"List approval decisions","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"query","name":"version","schema":{"type":"integer"},"description":"Version number. Defaults to latest."}],"tags":["Approvals"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1Review"}},"examples":{"OneHumanDecisionOnVersion2":{"value":[{"id":"e2d9a0c7-0b11-4b9e-9d0a-1fe7c7a5d4b2","type":"human","decision":"changes_requested","comment":"Please add weld callouts before approval.","version_number":2,"created_by":{"email":"martha.mechanical@example.com","first_name":"Martha","last_name":"Mechanical"},"created_at":"2026-04-20T09:05:15.123456Z"}],"summary":"Approval decisions for a drawing version"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/pdf/":{"get":{"operationId":"download_pdf","description":"Download the original PDF bytes for a drawing version. Defaults to the latest version. The response body is the raw PDF; handle it as a binary download, not JSON. The filename is supplied via the `Content-Disposition` header as `attachment; filename=\"<drawing-name>_v<version>.pdf\"`.","summary":"Download original PDF","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"query","name":"version","schema":{"type":"integer"},"description":"Version number. Defaults to latest."}],"tags":["Downloads"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/pdf":{"schema":{"type":"string","format":"binary"},"examples":{"PDFDownload":{"value":"%PDF-1.4\n%...\n<binary PDF bytes>\n%%EOF","summary":"Leading bytes of the returned PDF"}}}},"description":"Raw PDF bytes. Save to disk or pipe to a PDF viewer. The filename is supplied via the `Content-Disposition` header as `attachment; filename=\"<drawing-name>_v<version>.pdf\"`."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"The version's PDF is not available: DXF conversion still running or failed."}}}},"/drawings/{drawing_id}/dxf/":{"get":{"operationId":"download_dxf","description":"Download the original DXF file for a drawing version that was uploaded as DXF. Defaults to the latest version. Returns 404 when the version was uploaded as a PDF. The response body is the raw DXF; handle it as a binary download, not JSON (the filename comes via the `Content-Disposition` header, see the 200 response).","summary":"Download original DXF","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"query","name":"version","schema":{"type":"integer"},"description":"Version number. Defaults to latest."}],"tags":["Downloads"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/dxf":{"schema":{"type":"string","format":"binary"},"examples":{"DXFDownload":{"value":"0\nSECTION\n2\nHEADER\n...\n0\nSECTION\n2\nENTITIES\n...\n0\nEOF","summary":"Leading tags of the returned DXF"}}}},"description":"Raw DXF bytes, exactly as uploaded. The filename is supplied via the `Content-Disposition` header as `attachment; filename=\"<drawing-name>_v<version>.dxf\"`."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"Codes: \"version_not_found\", or \"no_dxf_original\" for PDF-origin versions."}}}},"/drawings/{drawing_id}/annotated-pdf/":{"get":{"operationId":"download_annotated_pdf","description":"Download the PDF with numbered pins marking each comment's position on the drawing; appended pages list the full comments and reviews — human and AI — by pin number. Defaults to the latest version. The response body is the raw PDF; handle it as a binary download, not JSON. The filename is supplied via the `Content-Disposition` header as `attachment; filename=\"<drawing-name>_v<version>_annotated.pdf\"`.","summary":"Download annotated PDF","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"query","name":"language","schema":{"type":"string"},"description":"UI language for the appended summary pages' labels (headings, status/decision words). One of `en`, `de`, `nl`; defaults to `en`. Comment and review text is returned in the language it was written in."},{"in":"query","name":"version","schema":{"type":"integer"},"description":"Version number. Defaults to latest."}],"tags":["Downloads"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/pdf":{"schema":{"type":"string","format":"binary"},"examples":{"AnnotatedPDFDownload":{"value":"%PDF-1.4\n%...\n<binary PDF bytes with annotation overlay>\n%%EOF","summary":"Leading bytes of the returned annotated PDF"}}}},"description":"Raw annotated PDF bytes. Save to disk or pipe to a PDF viewer. The filename is supplied via the `Content-Disposition` header as `attachment; filename=\"<drawing-name>_v<version>_annotated.pdf\"`."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"The version's PDF is not available: DXF conversion still running or failed."}}}},"/review-profiles/":{"get":{"operationId":"list_review_profiles","description":"List the AI review profiles configured for your team. Pass a review profile ID to the trigger AI review endpoint to customize the review.","summary":"List review profiles","tags":["AI Reviews"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/V1ReviewProfile"}},"examples":{"TwoProfiles":{"value":[{"id":"0c7d2c2c-8d4f-4f7e-9b3c-1a4e9f8a2b21","name":"Sheet metal — fabrication-ready","instructions":"Focus on bend allowances, hole-to-edge distances, and weld symbol completeness. Flag missing bend lines, missing flat-pattern dimensions, and any callouts that lack tolerance."},{"id":"e8a39ef5-6d3a-4d75-9d2e-7f1f8d92ab44","name":"DFM Cost Reduction","instructions":"Identify opportunities to reduce manufacturing cost. Flag tolerances tighter than the function requires. Flag surface-finish callouts specified more strictly than necessary. Flag features that drive expensive operations (small internal radii, non-standard hole or thread sizes, features requiring multiple machine setups). For each finding, suggest a relaxed alternative that preserves design intent."}],"summary":"Typical team with one custom profile and one starter library profile"}}}},"description":""}}}},"/drawings/{drawing_id}/ai-review/":{"post":{"operationId":"trigger_ai_review","description":"Trigger an AI review on a drawing. Consumes one AI credit. Reviews typically complete in 1–3 minutes; poll the AI review status endpoint every 5–10 seconds to check when it completes. Once status is `completed`, each finding includes a `comment_id` linking to the materialized Comment row — fetch full UI-ready data (xy position, status, replies) via `GET /api/v1/drawings/{drawing_id}/comments/{comment_id}/`. Use the optional `language` field to set the output language (default: `en`). Pass either `review_profile_id` (to apply a stored review profile) or `custom_instructions` (an ad-hoc one-off prompt that is not saved as a reusable profile), but not both.\n\nDXF uploads: a review may be triggered while the DXF-to-PDF conversion is still running — it starts automatically once the conversion completes. A failed conversion fails the review (`error_code` \"conversion_failed\", credit refunded); a version whose conversion has already failed is rejected (409).","summary":"Trigger AI review","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["AI Reviews"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1TriggerReviewRequest"},"examples":{"TriggerAReviewInEnglish":{"value":{"version":2,"language":"en"},"summary":"Request body for a typical AI review call"},"TriggerWithAd-hocCustomInstructions":{"value":{"version":2,"language":"en","custom_instructions":"Focus on ASME Y14.5 GD&T compliance. Flag any datum reference frame that is incomplete or any feature control frame missing a material condition modifier."},"summary":"Send a one-off custom prompt without creating a stored review profile"}}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/V1TriggerReviewRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1TriggerReviewRequest"}}}},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1AIReviewRun"},"examples":{"ReviewQueued":{"value":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"pending","version_number":2,"pen":{"version":""},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":null,"error_message":"","findings":[],"summary":"","recommendation":""},"summary":"AI review has been queued for processing"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"InvalidVersion":{"value":{"error":"Invalid version number.","error_code":"invalid_version"},"summary":"Version number is not an integer"},"ConflictingInstructions":{"value":{"error":"Provide either review_profile_id or custom_instructions, not both.","error_code":"conflicting_instructions"},"summary":"Both review_profile_id and custom_instructions provided"}}}},"description":"Invalid input; distinctive codes: \"invalid_version\", \"blank_custom_instructions\", \"custom_instructions_too_long\", \"conflicting_instructions\"."},"402":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"NoCredits":{"value":{"error":"You've used all AI reviews in your plan for this billing period.","error_code":"cap_reached"},"summary":"AI credits exhausted"}}}},"description":"No AI credit available; codes: \"trial_expired\", \"no_plan\", \"upgrade_required\", \"blocked\", \"cap_reached\"."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"DrawingOrVersionNotFound":{"value":{"error":"No versions found.","error_code":"version_not_found"},"summary":"Drawing ID does not belong to this team, or the version does not exist"}}}},"description":""},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"The version's DXF conversion has already failed (error_code \"conversion_failed\"), so a review can never run on it. A still-converting version is accepted (201) and the review starts when the conversion completes."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"RateLimited":{"value":{"error":"Request was throttled. Expected available in 42 seconds."},"summary":"Too many requests"}}}},"description":""},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"AIReviewServiceUnavailable":{"value":{"error":"AI review service is currently unavailable. Please try again later.","error_code":"celery_unavailable"},"summary":"Background worker is down — retry after a short delay"}}}},"description":""}}}},"/drawings/{drawing_id}/ai-review/{run_id}/":{"get":{"operationId":"get_ai_review_status","description":"Poll the status of an AI review run. When status is `completed`, the response includes findings, summary, and recommendation. Each finding carries a `comment_id` linking to the materialized Comment row — fetch full UI-ready data (xy position, status, replies) via `GET /api/v1/drawings/{drawing_id}/comments/{comment_id}/`.","summary":"Get AI review status","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"path","name":"run_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["AI Reviews"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1AIReviewRun"},"examples":{"Running":{"value":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"running","version_number":2,"pen":{"version":""},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":null,"error_message":"","findings":[],"summary":"","recommendation":""},"summary":"Review is still in progress"},"Completed":{"value":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"completed","version_number":2,"pen":{"version":"1.1"},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":"2026-04-20T09:34:08.901521Z","error_message":"","findings":[{"comment_id":"1f4fd6a6-1a5e-4f8a-a0a8-e9a5f1f0a431","page":2,"issue":"Missing tolerance on the 200 mm base plate dimension. Critical dimensions should have explicit tolerances.","confidence":"High"},{"comment_id":"9c2c9a6e-3a52-4bf3-a8a4-67d2c8c3a5c7","page":1,"issue":"Fillet welds shown on the drawing but no weld symbol or size specified. Add weld callouts before manufacturing.","confidence":"High"}],"summary":"Drawing is mostly clear; tolerance on the base plate and weld callouts are missing and should be added before manufacturing.","recommendation":"changes_requested"},"summary":"Review completed with findings"},"Failed":{"value":{"review_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"failed","version_number":2,"pen":{"version":""},"created_at":"2026-04-20T09:32:17.000000Z","completed_at":"2026-04-20T09:32:24.000000Z","error_message":"Failed to process PDF file.","findings":[],"summary":"","recommendation":""},"summary":"Review failed due to an error"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/ai-review/batch/":{"post":{"operationId":"batch_trigger_ai_review","description":"Queue AI reviews on up to 20 drawings in a single call. Each drawing consumes one AI credit. Validation errors (malformed payload, batch too large, conflicting prompt fields) fail the whole request with 400. Per-drawing failures (drawing not found, version not found, credit exhausted) are returned per item — successful items are still queued. Each review typically completes in 1–3 minutes. Poll `POST /api/v1/ai-review/status/` every 5–10 seconds with the returned `review_id` values to fetch all run statuses in one call (preferred for batch). The per-review status endpoint also works for checking an individual run. Pass either `review_profile_id` or `custom_instructions` (not both) to apply the same prompt to every item in the batch.","summary":"Trigger AI reviews on a batch of drawings","tags":["AI Reviews"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1BatchTriggerReviewRequest"},"examples":{"BatchTrigger":{"value":{"drawings":[{"drawing_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251"},{"drawing_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","version":"2"},{"drawing_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479"}],"custom_instructions":"Focus on weld symbol completeness.","language":"en"},"summary":"Trigger reviews on three drawings with a one-off custom prompt"}}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/V1BatchTriggerReviewRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1BatchTriggerReviewRequest"}}},"required":true},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1BatchTriggerReviewResponse"},"examples":{"Partial-successResponse":{"value":{"reviews":[{"drawing_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","review_id":"1f2c5d4a-1234-5678-9abc-def012345678","status":"pending"},{"drawing_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","review_id":"2a3b4c5d-2345-6789-abcd-ef0123456789","status":"pending"},{"drawing_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","error":"You've used all AI reviews in your plan for this billing period.","error_code":"cap_reached"}]},"summary":"Two drawings queued, one failed credit check"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"BatchTooLarge":{"value":{"error":"Batch exceeds maximum of 20 drawings per request (received 50).","error_code":"batch_too_large"},"summary":"More than the maximum allowed drawings"},"ConflictingInstructions":{"value":{"error":"Provide either review_profile_id or custom_instructions, not both.","error_code":"conflicting_instructions"},"summary":"Both review_profile_id and custom_instructions provided"}}}},"description":""},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/ai-review/status/":{"post":{"operationId":"batch_ai_review_status","description":"Look up the status of multiple AI review runs in one request. Pass 1–20 review IDs; receive each one's current status, findings, summary, and recommendation in the same shape as the single-review status endpoint. Unknown IDs and IDs belonging to other teams are silently omitted from the response — diff against the input list to detect missing entries. Designed to collapse the polling fan-out after `batch_trigger_ai_review`: one status call per polling round instead of N. Once a run completes, each finding carries a `comment_id` linking to the materialized Comment row — fetch full UI-ready data (xy position, status, replies) via `GET /api/v1/drawings/{drawing_id}/comments/{comment_id}/`.","summary":"Get status for a batch of AI reviews","tags":["AI Reviews"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1BatchStatusRequest"},"examples":{"StatusRequest":{"value":{"review_ids":["19b5666a-4ec7-4c1f-b3f0-24dbf6d0f0a1","72f3499e-1962-4d3e-8a1d-1f4d5b9d2c77","c8a1e92b-7b9f-4f7d-a0b3-6d2e91f4aa35"]},"summary":"Look up three review run IDs returned by a prior batch trigger"}}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/V1BatchStatusRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1BatchStatusRequest"}}},"required":true},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1BatchStatusResponse"},"examples":{"MixedStatuses":{"value":{"reviews":[{"review_id":"19b5666a-4ec7-4c1f-b3f0-24dbf6d0f0a1","status":"running","version_number":1,"pen":{"version":""},"created_at":"2026-04-27T09:32:17.000000Z","completed_at":null,"error_message":"","findings":[],"summary":"","recommendation":""},{"review_id":"72f3499e-1962-4d3e-8a1d-1f4d5b9d2c77","status":"completed","version_number":2,"pen":{"version":"1.1"},"created_at":"2026-04-27T09:30:11.000000Z","completed_at":"2026-04-27T09:31:48.000000Z","error_message":"","findings":[{"comment_id":"1f4fd6a6-1a5e-4f8a-a0a8-e9a5f1f0a431","page":1,"issue":"Missing perpendicularity tolerance on the upright.","confidence":"High"}],"summary":"Drawing is mostly complete but lacks an angular tolerance.","recommendation":"changes_requested"},{"review_id":"c8a1e92b-7b9f-4f7d-a0b3-6d2e91f4aa35","status":"failed","version_number":1,"pen":{"version":""},"created_at":"2026-04-27T09:29:55.000000Z","completed_at":"2026-04-27T09:30:42.000000Z","error_message":"AI provider returned an error.","findings":[],"summary":"","recommendation":""}]},"summary":"One running, one completed, one failed (unknown IDs omitted)"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"EmptyReviewIds":{"value":{"error":"Field 'review_ids' must not be empty.","error_code":"empty_review_ids"},"summary":"review_ids list is empty"},"BatchTooLarge":{"value":{"error":"Batch exceeds maximum of 20 review IDs per request (received 50).","error_code":"batch_too_large"},"summary":"More than the maximum allowed review IDs"}}}},"description":""},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/ask/":{"post":{"operationId":"ask_pen","description":"Ask Pen, ClearHandoff's AI assistant, a follow-up question about a drawing that has already been AI-reviewed. Async: this endpoint returns 202 immediately with a `question_id`; poll `GET /api/v1/drawings/{drawing_id}/ask/{question_id}/` until `status` is `completed` (or `failed`) to retrieve Pen's answer. Pen typically responds in 5–60 seconds; poll every ~3 seconds.\n\nPen follow-ups are included with the AI review credit on a drawing — no separate per-question charge. Each drawing supports up to 20 Pen questions across all of its versions. The drawing must already have a completed AI review — call the AI review trigger endpoint first if needed.\n\nWhere the answer lands depends on the optional target fields:\n- No `pin` / `parent_comment_id` / `review_id` → Pen replies on the most recent AI review for the drawing (drawing-level question).\n- `page` + `pin` → a new pinned comment is created with the question; Pen replies in that thread.\n- `parent_comment_id` → Pen replies in that existing comment thread.\n- `review_id` → Pen replies in that existing review thread.","summary":"Ask Pen a follow-up question after an AI review","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["AI Reviews"],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1AskPenRequest"},"examples":{"Drawing-levelQuestion":{"value":{"question":"Are all critical dimensions toleranced?"},"summary":"Ask Pen something about the drawing without picking a location"},"PinnedQuestion":{"value":{"question":"What's the tolerance on this hole?","page":2,"pin":{"x_percent":42.5,"y_percent":67.1}},"summary":"Ask Pen about a specific spot on a page"}}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/V1AskPenRequest"}},"multipart/form-data":{"schema":{"$ref":"#/components/schemas/V1AskPenRequest"}}},"required":true},"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1PenQuestion"},"examples":{"QuestionAccepted":{"value":{"question_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"pending","target_type":"review_reply","question_artifact_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","answer_artifact_id":null,"answer":"","error_message":"","created_at":"2026-05-04T08:30:00.000000Z","completed_at":null},"summary":"202 response — poll question_id for the answer"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"},"examples":{"NoPriorAIReview":{"value":{"error":"This drawing has no AI review yet. Trigger an AI review before asking Pen.","error_code":"no_ai_review"},"summary":"Drawing has no AI findings to ground Pen on yet"},"Per-drawingCapReached":{"value":{"error":"This drawing has reached its limit of 20 Pen questions across all of its versions. Ask Pen on a different drawing.","error_code":"pen_cap_reached"},"summary":"20 Pen questions already asked on this drawing"}}}},"description":""},"403":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":"The version's PDF is not available: DXF conversion still running or failed (error_code \"conversion_pending\" / \"conversion_failed\")."},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/ask/{question_id}/":{"get":{"operationId":"pen_question_status","description":"Look up the current state of a Pen question submitted via `POST /api/v1/drawings/{drawing_id}/ask/`. Pen typically completes in 5–60 seconds; poll every ~3 seconds. Statuses:\n- `pending` — accepted, not yet picked up by a worker.\n- `running` — the LLM call is in flight.\n- `completed` — `answer` and `answer_artifact_id` are populated.\n- `failed` — `error_message` is populated; `answer` is empty.\n","summary":"Poll the status of a Pen question","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"path","name":"question_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["AI Reviews"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1PenQuestion"},"examples":{"StillRunning":{"value":{"question_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"running","target_type":"review_reply","question_artifact_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","answer_artifact_id":null,"answer":"","error_message":"","created_at":"2026-05-04T08:30:00.000000Z","completed_at":null},"summary":"Worker has the job; answer not ready yet"},"CompletedAnswer":{"value":{"question_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"completed","target_type":"review_reply","question_artifact_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","answer_artifact_id":"1f4fd6a6-1a5e-4f8a-a0a8-e9a5f1f0a431","answer":"The drawing shows a ±0.1 mm general tolerance applied via the title block. The Ø12 hole inherits that tolerance — there is no tighter feature-specific tolerance shown on this page.","error_message":"","created_at":"2026-05-04T08:30:00.000000Z","completed_at":"2026-05-04T08:30:18.124000Z"},"summary":"Pen has replied — answer text is populated"},"Failed":{"value":{"question_id":"84386e1a-fdf1-4834-9eb5-8dc9dfecc251","status":"failed","target_type":"review_reply","question_artifact_id":"9c5fbb02-1e93-4b1b-9a18-9c08d3a51f7e","answer_artifact_id":null,"answer":"","error_message":"Pen failed to produce an answer. Please retry.","created_at":"2026-05-04T08:30:00.000000Z","completed_at":"2026-05-04T08:30:24.812000Z"},"summary":"Pen could not produce an answer; client should retry"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/comments/":{"get":{"operationId":"list_comments","description":"List all comments on a drawing version. Defaults to the latest version. Includes both human and AI comments.","summary":"List comments","parameters":[{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"query","name":"limit","schema":{"type":"integer"},"description":"Maximum number of comments to return (max 100, default 100). Most drawings have only a handful of comments, so the default returns everything in one response."},{"in":"query","name":"offset","schema":{"type":"integer"},"description":"Number of comments to skip before returning results (default 0). For drawings with more than 100 comments, follow the `next` and `previous` URLs in the response instead of computing offsets manually."},{"in":"query","name":"status","schema":{"type":"string","enum":["open","resolved"]},"description":"Filter by status: \"open\" or \"resolved\". Returns all if omitted."},{"in":"query","name":"version","schema":{"type":"integer"},"description":"Version number. Defaults to latest."}],"tags":["Comments"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedCommentList"},"examples":{"TwoCommentsOnPage2AndPage1":{"value":{"count":2,"next":null,"previous":null,"results":[{"id":"502bafac-077c-4406-a0cc-5a1dadee973c","page":2,"x_percent":48.3,"y_percent":56.8,"text":"Missing tolerance on the 200 mm base plate dimension.","status":"open","source":"ai","ai_confidence":"High","created_by":null,"created_at":"2026-04-20T09:34:08.901521Z","replies":[]},{"id":"0a7459f8-f386-4259-bed4-f8c12f74c493","page":1,"x_percent":33.2,"y_percent":12.7,"text":"Please add weld callouts.","status":"resolved","source":"human","ai_confidence":"","created_by":{"email":"martha.mechanical@example.com","first_name":"Martha","last_name":"Mechanical"},"created_at":"2026-04-20T09:40:11.321000Z","replies":[{"id":"b1e82f94-6c2d-4a11-9f3b-7a4b9ef88c21","text":"Makes sense, I'll add it in rev C.","source":"human","created_by":{"email":"eddie.engineer@example.com","first_name":"Eddie","last_name":"Engineer"},"created_at":"2026-04-20T10:02:47.118000Z"}]}]},"summary":"Paginated list of comments"}}}},"description":""},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/comments/{comment_id}/":{"get":{"operationId":"get_comment","description":"Fetch a single comment by id. Returns the full comment payload including xy position, status, source (`ai` or `human`), AI confidence, replies, and creator. Useful when you have a `comment_id` from an AI review's `findings` and want the full UI-ready data without paginating the list endpoint.","summary":"Get a comment","parameters":[{"in":"path","name":"comment_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Comments"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Comment"},"examples":{"AICommentWithOneHumanReply":{"value":{"id":"502bafac-077c-4406-a0cc-5a1dadee973c","page":2,"x_percent":48.3,"y_percent":56.8,"text":"Missing tolerance on the 200 mm base plate dimension.","status":"open","source":"ai","ai_confidence":"High","created_by":null,"created_at":"2026-04-20T09:34:08.901521Z","replies":[{"id":"ad6a31f4-c4d6-4f1f-9caa-95c93e8b6a7c","text":"Intentional — the 200 mm is a stock cut length, not a critical dim.","source":"human","created_by":{"email":"lukas@example.com","first_name":"Lukas","last_name":"Meyer"},"created_at":"2026-04-20T10:12:44.115228Z"}]},"summary":"A comment from an AI review that an engineer replied to"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/comments/{comment_id}/resolve/":{"post":{"operationId":"resolve_comment","description":"Mark a comment as resolved.","summary":"Resolve a comment","parameters":[{"in":"path","name":"comment_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Comments"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Comment"},"examples":{"ResolvedAIComment":{"value":{"id":"502bafac-077c-4406-a0cc-5a1dadee973c","page":2,"x_percent":48.3,"y_percent":56.8,"text":"Missing tolerance on the 200 mm base plate dimension.","status":"resolved","source":"ai","ai_confidence":"High","created_by":null,"created_at":"2026-04-20T09:34:08.901521Z","replies":[]},"summary":"AI finding dismissed as a false positive"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/drawings/{drawing_id}/comments/{comment_id}/reopen/":{"post":{"operationId":"reopen_comment","description":"Reopen a previously resolved comment.","summary":"Reopen a comment","parameters":[{"in":"path","name":"comment_id","schema":{"type":"string","format":"uuid"},"required":true},{"in":"path","name":"drawing_id","schema":{"type":"string","format":"uuid"},"required":true}],"tags":["Comments"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Comment"},"examples":{"ReopenedHumanComment":{"value":{"id":"0a7459f8-f386-4259-bed4-f8c12f74c493","page":1,"x_percent":33.2,"y_percent":12.7,"text":"Please add weld callouts.","status":"open","source":"human","ai_confidence":"","created_by":{"email":"martha.mechanical@example.com","first_name":"Martha","last_name":"Mechanical"},"created_at":"2026-04-20T09:40:11.321000Z","replies":[{"id":"b1e82f94-6c2d-4a11-9f3b-7a4b9ef88c21","text":"Makes sense, I'll add it in rev C.","source":"human","created_by":{"email":"eddie.engineer@example.com","first_name":"Eddie","last_name":"Engineer"},"created_at":"2026-04-20T10:02:47.118000Z"}]},"summary":"A previously-resolved human comment flipped back to open"}}}},"description":""},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Error"}}},"description":""}}}},"/credits/":{"get":{"operationId":"get_credits","description":"Check remaining AI review credits for the current billing period.","summary":"Get AI credit balance","tags":["Account"],"security":[{"bearerAuth":[]},{"bearerAuth":[]}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/V1Credits"},"examples":{"ProPlanMid-cycle":{"value":{"plan":"pro","credits_included":50,"credits_used":18,"credits_remaining":32,"bonus_credits":0,"credits_reset_date":"2026-05-04T00:00:00Z"},"summary":"Pro team partway through their billing period"}}}},"description":""}}}}},"components":{"schemas":{"AIRunStatusEnum":{"enum":["pending","running","completed","failed"],"type":"string","description":"* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed"},"BlankEnum":{"enum":[""]},"CommentStatusEnum":{"enum":["open","resolved"],"type":"string","description":"* `open` - Open\n* `resolved` - Resolved"},"ConfidenceEnum":{"enum":["High","Medium","Low"],"type":"string","description":"* `High` - High\n* `Medium` - Medium\n* `Low` - Low"},"DecisionEnum":{"enum":["approved","changes_requested","comment"],"type":"string","description":"* `approved` - Approved\n* `changes_requested` - Changes Requested\n* `comment` - Comment"},"PaginatedCommentList":{"type":"object","properties":{"count":{"type":"integer","description":"Total number of comments on this drawing version."},"next":{"type":"string","format":"uri","nullable":true,"description":"URL to fetch the next page of comments, or null if this is the last page."},"previous":{"type":"string","format":"uri","nullable":true,"description":"URL to fetch the previous page of comments, or null if this is the first page."},"results":{"type":"array","items":{"$ref":"#/components/schemas/V1Comment"}}},"required":["count","next","previous","results"]},"PaginatedDrawingList":{"type":"object","properties":{"count":{"type":"integer","description":"Total number of drawings on this team."},"next":{"type":"string","format":"uri","nullable":true,"description":"URL to fetch the next page of drawings, or null if this is the last page."},"previous":{"type":"string","format":"uri","nullable":true,"description":"URL to fetch the previous page of drawings, or null if this is the first page."},"results":{"type":"array","items":{"$ref":"#/components/schemas/V1DrawingList"}}},"required":["count","next","previous","results"]},"PatchedV1RenameDrawingRequest":{"type":"object","properties":{"name":{"type":"string","description":"New drawing name."}}},"RecommendationEnum":{"type":"string","enum":["approved","changes_requested"]},"TargetTypeEnum":{"enum":["comment","comment_reply","review_reply"],"type":"string","description":"* `comment` - Comment\n* `comment_reply` - Comment reply\n* `review_reply` - Review reply"},"V1AIReviewRun":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid"},"status":{"$ref":"#/components/schemas/AIRunStatusEnum"},"version_number":{"type":"integer"},"pen":{"allOf":[{"$ref":"#/components/schemas/V1Pen"}],"readOnly":true},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"completed_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-07-28T10:00:00Z"},"error_message":{"type":"string"},"error_code":{"type":"string","readOnly":true,"description":"Stable machine-readable failure cause; empty unless status is \"failed\" and the failure has a programmatic cause. Currently only \"conversion_failed\": the review was triggered on a DXF whose conversion later failed — the credit was refunded."},"findings":{"type":"array","items":{"$ref":"#/components/schemas/V1Finding"},"readOnly":true},"summary":{"type":"string","readOnly":true},"recommendation":{"description":"AI's verdict on the drawing. Empty string until the run completes. After completion: `approved` if the AI thinks the drawing is fit for release, or `changes_requested` if it identified issues that need fixing.","readOnly":true,"oneOf":[{"$ref":"#/components/schemas/RecommendationEnum"},{"$ref":"#/components/schemas/BlankEnum"}]}},"required":["created_at","error_code","findings","pen","recommendation","review_id","summary","version_number"]},"V1AskPenRequest":{"type":"object","properties":{"question":{"type":"string","description":"The question to ask Pen. Max 5,000 characters.","maxLength":5000},"version":{"type":"integer","nullable":true,"description":"Version number to pin a new Comment on. Only used together with `page` + `pin`; ignored when targeting `parent_comment_id` / `review_id` (those carry their own version). Defaults to the latest version."},"page":{"type":"integer","minimum":1,"nullable":true,"description":"Page number for a pinned question. Required when `pin` is provided."},"pin":{"allOf":[{"$ref":"#/components/schemas/V1PenPin"}],"nullable":true,"description":"Pin coordinates {x_percent, y_percent}. When provided with `page`, the question is created as a pinned comment at that location. When omitted, the question is posted as a reply on the most recent AI review (or the target identified by `parent_comment_id` / `review_id`)."},"parent_comment_id":{"type":"string","format":"uuid","nullable":true,"description":"Reply under an existing comment thread. Mutually exclusive with `pin` and `review_id`."},"review_id":{"type":"string","format":"uuid","nullable":true,"description":"Reply under a specific review. Mutually exclusive with `pin` and `parent_comment_id`."}},"required":["question"]},"V1BatchReviewItemRequest":{"type":"object","description":"Per-item shape for the batch AI review endpoint (docs-only).\n\n``drawing_id`` and ``version`` are intentionally typed as ``CharField``\nrather than the \"stronger\" ``UUIDField`` / ``IntegerField``. The batch\nendpoint is designed around partial success: malformed values are\nreported as per-item ``error_code`` (``invalid_drawing_id``,\n``invalid_version``) inside a 201 response, not as a request-wide 400.\nOne bad item should not abort the other 19.\n\ndrf-spectacular emits ``format: uuid`` / ``type: integer`` for the\nstronger types, and most generated SDKs validate against those\n*before* sending the request — which pre-empts the per-item-error\npath. Clients would get a client-side type error and never see the\npartial-success contract the endpoint advertises.\n\nThese serializers are docs-only (the view does not call\n``is_valid()``), so the schema is purely a communication tool.\n``CharField`` is the most honest signal: \"send a string; the server\ntells you which items it could not parse.\" The field help text spells\nout the per-item validation semantics for human readers.","properties":{"drawing_id":{"type":"string","description":"Drawing UUID as a string. Validated per item during batch processing; malformed values are reported in that item's `error_code` (e.g. `invalid_drawing_id`) rather than failing the whole request."},"version":{"type":"string","description":"Version number. Defaults to the latest version. Validated per item during batch processing; malformed values are reported in that item's `error_code` (e.g. `invalid_version`) rather than failing the whole request."}},"required":["drawing_id"]},"V1BatchReviewItemResponse":{"type":"object","properties":{"drawing_id":{"type":"string","description":"Drawing identifier supplied for this batch item. Normally a UUID string, but for items rejected with `invalid_drawing_id` the raw input is echoed back unchanged."},"review_id":{"type":"string","format":"uuid","nullable":true},"status":{"type":"string","description":"Run status when queued (e.g., \"pending\"). Omitted if the item failed."},"error":{"type":"string","description":"Human-readable error message if this item could not be queued."},"error_code":{"type":"string","description":"Machine-readable error code if this item could not be queued."}},"required":["drawing_id"]},"V1BatchStatusRequest":{"type":"object","properties":{"review_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"List of review run IDs to look up (1–20 per request). Unknown IDs and IDs belonging to other teams are silently omitted from the response — diff against the input list to detect missing entries.","maxItems":20,"minItems":1}},"required":["review_ids"]},"V1BatchStatusResponse":{"type":"object","properties":{"reviews":{"type":"array","items":{"$ref":"#/components/schemas/V1AIReviewRun"}}},"required":["reviews"]},"V1BatchTriggerReviewRequest":{"type":"object","properties":{"drawings":{"type":"array","items":{"$ref":"#/components/schemas/V1BatchReviewItemRequest"},"description":"List of drawings to review (1–20 per request). Each item: {drawing_id, version?}.","maxItems":20,"minItems":1},"review_profile_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of a stored review profile applied to every drawing in the batch. Mutually exclusive with `custom_instructions`. Pass `null` or omit to use no profile."},"custom_instructions":{"type":"string","nullable":true,"description":"Ad-hoc custom instructions applied to every drawing in the batch. Max 5,000 characters. Mutually exclusive with `review_profile_id`. Pass `null` or omit to use no ad-hoc prompt.","maxLength":5000},"language":{"type":"string","description":"Output language for the AI reviews. Supported: en, de, nl, fr, es, it, pt, ja, ko, zh, pl, cs, sv, nb. Defaults to en.","maxLength":5}},"required":["drawings"]},"V1BatchTriggerReviewResponse":{"type":"object","properties":{"reviews":{"type":"array","items":{"$ref":"#/components/schemas/V1BatchReviewItemResponse"}}},"required":["reviews"]},"V1Comment":{"type":"object","description":"Public v1 representation of a comment.","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"page":{"type":"integer","maximum":2147483647,"minimum":0},"x_percent":{"type":"number","format":"double"},"y_percent":{"type":"number","format":"double"},"text":{"type":"string"},"status":{"$ref":"#/components/schemas/CommentStatusEnum"},"source":{"$ref":"#/components/schemas/V1CommentSourceEnum"},"ai_confidence":{"type":"string","maxLength":10},"created_by":{"allOf":[{"$ref":"#/components/schemas/V1User"}],"nullable":true},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"replies":{"type":"array","items":{"$ref":"#/components/schemas/V1Reply"},"readOnly":true}},"required":["created_at","created_by","id","page","replies","text","x_percent","y_percent"]},"V1CommentSourceEnum":{"enum":["human","ai"],"type":"string","description":"* `human` - Human\n* `ai` - AI Suggestion"},"V1CreateDrawingRequest":{"type":"object","description":"Upload fields: the file plus the optional trigger_ai_review family.","properties":{"file":{"type":"string","format":"uri","description":"PDF (up to 20 MB) or DXF (up to 5 MB) file to upload. DXF files are converted to PDF asynchronously (the response reports pdf_status \"pending\")."},"trigger_ai_review":{"type":"boolean","description":"Also start an AI review of the uploaded file (consumes one AI credit; the response carries the run's `review_id` to poll). Atomic: if the review cannot be created (e.g. no credits), the whole upload is rejected and nothing is stored. For DXF uploads the review starts automatically once the conversion completes."},"review_profile_id":{"type":"string","format":"uuid","nullable":true,"description":"Review profile to apply; requires trigger_ai_review=true. Mutually exclusive with custom_instructions."},"custom_instructions":{"type":"string","nullable":true,"description":"Ad-hoc one-off review prompt (not saved as a reusable profile); requires trigger_ai_review=true. Max 5,000 characters. Mutually exclusive with review_profile_id.","maxLength":5000},"language":{"type":"string","description":"Review output language (default \"en\"); requires trigger_ai_review=true. Supported: en, de, nl, fr, es, it, pt, ja, ko, zh, pl, cs, sv, nb.","maxLength":5},"name":{"type":"string","description":"Drawing name. Defaults to the filename."}},"required":["file"]},"V1Credits":{"type":"object","properties":{"plan":{"type":"string","description":"Current plan: free, pro, or scale."},"credits_included":{"type":"integer","description":"Credits included per billing period."},"credits_used":{"type":"integer","description":"Credits used in the current period."},"credits_remaining":{"type":"integer","description":"Credits available (included + bonus - used)."},"bonus_credits":{"type":"integer","description":"Extra credits granted by admin."},"credits_reset_date":{"type":"string","format":"date-time","nullable":true,"description":"When credits will next reset (approx. 30 days from last reset).","example":"2026-07-28T10:00:00Z"}},"required":["bonus_credits","credits_included","credits_remaining","credits_reset_date","credits_used","plan"]},"V1DrawingDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"name":{"type":"string","maxLength":255},"url":{"type":"string","readOnly":true},"versions":{"type":"array","items":{"$ref":"#/components/schemas/V1Version"},"readOnly":true},"latest_version":{"allOf":[{"$ref":"#/components/schemas/V1LatestVersion"}],"nullable":true,"readOnly":true},"latest_ai_review":{"type":"object","additionalProperties":{},"nullable":true,"readOnly":true},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"updated_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"}},"required":["created_at","id","latest_ai_review","latest_version","name","updated_at","url","versions"]},"V1DrawingList":{"type":"object","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"name":{"type":"string","maxLength":255},"url":{"type":"string","readOnly":true},"version_count":{"type":"integer","readOnly":true},"latest_version":{"allOf":[{"$ref":"#/components/schemas/V1Version"}],"nullable":true,"readOnly":true},"approval_status":{"type":"string","nullable":true,"readOnly":true,"description":"Latest human approval decision on the current version: \"approved\", \"changes_requested\", or null if no decision yet. AI reviews are not reflected here. Use the /ai-review/ endpoints for AI review results."},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"updated_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"}},"required":["approval_status","created_at","id","latest_version","name","updated_at","url","version_count"]},"V1Error":{"type":"object","properties":{"error":{"type":"string","description":"Human-readable description of what went wrong."},"error_code":{"type":"string","description":"Stable machine-readable failure code for programmatic handling — see the 'Error handling' section of these docs for the full catalog."}},"required":["error"]},"V1Finding":{"type":"object","properties":{"comment_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the materialized Comment row for this finding. Returned only on `completed` runs (the API returns an empty `findings` list while the run is `pending` / `running`, so this field will not appear during those statuses). May be null on legacy runs that completed before this field was persisted into stored results. Use it to fetch the full UI-ready representation (xy position, status, source, replies on the AI finding) via `GET /api/v1/drawings/{drawing_id}/comments/{comment_id}/`, or to mutate the finding via the comment resolve / reopen endpoints."},"page":{"type":"integer","description":"Page number on which the finding applies."},"issue":{"type":"string","description":"Free-text description of the issue."},"confidence":{"allOf":[{"$ref":"#/components/schemas/ConfidenceEnum"}],"description":"Model's confidence that the finding is a real defect.\n\n* `High` - High\n* `Medium` - Medium\n* `Low` - Low"}},"required":["confidence","issue","page"]},"V1LatestVersion":{"type":"object","description":"The drawing's most recent published version, with direct URLs for\ndownloading its original and annotated PDFs.","properties":{"version_number":{"type":"integer","maximum":2147483647,"minimum":0},"page_count":{"type":"integer","nullable":true,"readOnly":true,"description":"Number of PDF pages. Set when pdf_status is \"completed\"; null while a DXF upload is converting (\"pending\"/\"running\") and after a failed conversion."},"pdf_status":{"allOf":[{"$ref":"#/components/schemas/AIRunStatusEnum"}],"description":"Readiness of this version's PDF: \"pending\" (queued), \"running\", \"completed\", or \"failed\" — the same status enum as AI review runs and Pen questions. Direct PDF uploads are always \"completed\". AI reviews wait for the conversion automatically; only PDF downloads require \"completed\".","readOnly":true},"pdf_error":{"type":"string","readOnly":true,"description":"Reason the conversion failed; empty unless pdf_status is \"failed\"."},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"original_pdf_url":{"type":"string","description":"Absolute URL of the original PDF download endpoint."},"annotated_pdf_url":{"type":"string","description":"Absolute URL of the annotated PDF download endpoint."}},"required":["annotated_pdf_url","created_at","original_pdf_url","page_count","pdf_error","pdf_status","version_number"]},"V1Pen":{"type":"object","description":"Pen, our AI reviewer, and the version that produced this review.","properties":{"version":{"type":"string","description":"Version of Pen that produced this review, e.g. \"1.1\". Empty until the review completes. Improvements to Pen are versioned: upgrades that change reviewer behavior ship as new versions."}}},"V1PenPin":{"type":"object","properties":{"x_percent":{"type":"number","format":"double","maximum":100,"minimum":0},"y_percent":{"type":"number","format":"double","maximum":100,"minimum":0}},"required":["x_percent","y_percent"]},"V1PenQuestion":{"type":"object","description":"Status payload for a /ask/ Pen question. Returned by both POST (with\nstatus=pending) and the GET status-poll endpoint.","properties":{"question_id":{"type":"string","format":"uuid","description":"Polling key — pass to GET /api/v1/drawings/{drawing_id}/ask/{question_id}/."},"status":{"$ref":"#/components/schemas/AIRunStatusEnum"},"target_type":{"allOf":[{"$ref":"#/components/schemas/TargetTypeEnum"}],"description":"What kind of artifact holds the question and Pen's reply.\n\n* `comment` - Comment\n* `comment_reply` - Comment reply\n* `review_reply` - Review reply"},"question_artifact_id":{"type":"string","format":"uuid","description":"ID of the artifact storing the user's question (Comment / Reply / ReviewReply)."},"answer_artifact_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the artifact storing Pen's reply. Null until status=completed."},"answer":{"type":"string","readOnly":true,"description":"Pen's answer text. Empty string until status=completed."},"error_message":{"type":"string"},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"},"completed_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-07-28T10:00:00Z"}},"required":["answer","answer_artifact_id","created_at","question_artifact_id","question_id","target_type"]},"V1Reply":{"type":"object","description":"Public v1 representation of a reply on a comment thread.","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"text":{"type":"string"},"source":{"$ref":"#/components/schemas/V1ReplySourceEnum"},"created_by":{"allOf":[{"$ref":"#/components/schemas/V1User"}],"nullable":true},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"}},"required":["created_at","created_by","id","text"]},"V1ReplySourceEnum":{"enum":["human","ai"],"type":"string","description":"* `human` - Human\n* `ai` - AI"},"V1Review":{"type":"object","description":"Unified serializer for both AI and human reviews.","properties":{"id":{"type":"string","format":"uuid","readOnly":true},"type":{"type":"string","readOnly":true},"decision":{"$ref":"#/components/schemas/DecisionEnum"},"comment":{"type":"string"},"version_number":{"type":"integer"},"created_by":{"allOf":[{"$ref":"#/components/schemas/V1User"}],"nullable":true},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"}},"required":["created_at","created_by","decision","id","type","version_number"]},"V1ReviewProfile":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"instructions":{"type":"string"}},"required":["id","instructions","name"]},"V1TriggerReviewRequest":{"type":"object","properties":{"version":{"type":"integer","description":"Version number. Defaults to the latest version."},"review_profile_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of a stored review profile to apply on top of the base review prompt. Mutually exclusive with `custom_instructions`. Pass `null` or omit to use no profile."},"custom_instructions":{"type":"string","nullable":true,"description":"Ad-hoc custom instructions appended to the base review prompt for this run only. Not saved as a reusable review profile. Max 5,000 characters. Mutually exclusive with `review_profile_id`. Pass `null` or omit to use no ad-hoc prompt.","maxLength":5000},"language":{"type":"string","description":"Output language for the AI review (ISO 639-1 code). Supported: en, de, nl, fr, es, it, pt, ja, ko, zh, pl, cs, sv, nb. Defaults to en.","maxLength":5}}},"V1UploadResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string"},"version_number":{"type":"integer"},"page_count":{"type":"integer","nullable":true,"description":"Number of PDF pages. Always set for direct PDF uploads; null while a DXF upload is still converting."},"review_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the AI review run created when trigger_ai_review=true was passed; null otherwise. Poll GET /api/v1/drawings/{drawing_id}/ai-review/{review_id}/ until its status is \"completed\" or \"failed\"."},"pdf_status":{"allOf":[{"$ref":"#/components/schemas/AIRunStatusEnum"}],"description":"Readiness of the version's PDF: \"completed\" for direct PDF uploads, \"pending\" for DXF uploads. AI reviews may be triggered right away; only PDF downloads require \"completed\".\n\n* `pending` - Pending\n* `running` - Running\n* `completed` - Completed\n* `failed` - Failed"},"created_at":{"type":"string","format":"date-time","example":"2026-07-28T10:00:00Z"}},"required":["created_at","id","name","page_count","pdf_status","review_id","url","version_number"]},"V1UploadVersionRequest":{"type":"object","description":"Upload fields: the file plus the optional trigger_ai_review family.","properties":{"file":{"type":"string","format":"uri","description":"PDF (up to 20 MB) or DXF (up to 5 MB) file to upload. DXF files are converted to PDF asynchronously (the response reports pdf_status \"pending\")."},"trigger_ai_review":{"type":"boolean","description":"Also start an AI review of the uploaded file (consumes one AI credit; the response carries the run's `review_id` to poll). Atomic: if the review cannot be created (e.g. no credits), the whole upload is rejected and nothing is stored. For DXF uploads the review starts automatically once the conversion completes."},"review_profile_id":{"type":"string","format":"uuid","nullable":true,"description":"Review profile to apply; requires trigger_ai_review=true. Mutually exclusive with custom_instructions."},"custom_instructions":{"type":"string","nullable":true,"description":"Ad-hoc one-off review prompt (not saved as a reusable profile); requires trigger_ai_review=true. Max 5,000 characters. Mutually exclusive with review_profile_id.","maxLength":5000},"language":{"type":"string","description":"Review output language (default \"en\"); requires trigger_ai_review=true. Supported: en, de, nl, fr, es, it, pt, ja, ko, zh, pl, cs, sv, nb.","maxLength":5}},"required":["file"]},"V1User":{"type":"object","description":"Public v1 representation of a user (comment/review author).","properties":{"email":{"type":"string","format":"email"},"first_name":{"type":"string"},"last_name":{"type":"string"}},"required":["email","first_name","last_name"]},"V1Version":{"type":"object","properties":{"version_number":{"type":"integer","maximum":2147483647,"minimum":0},"page_count":{"type":"integer","nullable":true,"readOnly":true,"description":"Number of PDF pages. Set when pdf_status is \"completed\"; null while a DXF upload is converting (\"pending\"/\"running\") and after a failed conversion."},"pdf_status":{"allOf":[{"$ref":"#/components/schemas/AIRunStatusEnum"}],"description":"Readiness of this version's PDF: \"pending\" (queued), \"running\", \"completed\", or \"failed\" — the same status enum as AI review runs and Pen questions. Direct PDF uploads are always \"completed\". AI reviews wait for the conversion automatically; only PDF downloads require \"completed\".","readOnly":true},"pdf_error":{"type":"string","readOnly":true,"description":"Reason the conversion failed; empty unless pdf_status is \"failed\"."},"created_at":{"type":"string","format":"date-time","readOnly":true,"example":"2026-07-28T10:00:00Z"}},"required":["created_at","page_count","pdf_error","pdf_status","version_number"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key in format: ch_<40 hex chars>"}}},"tags":[{"name":"Quickstart","x-traitTag":true,"description":"Upload a drawing and start an AI review in one call, then poll the review until it completes. Create an API key on your team page first.\n\n### Python\n\n```python\nimport time\nimport requests\n\nAPI_KEY = \"ch_YOUR_KEY\"  # Create one on your team page; use an env var in real code\n\n# 1. Upload a drawing and start an AI review in one call\nresponse = requests.post(\n    \"https://www.clearhandoff.com/api/v1/drawings/\",\n    headers={\"Authorization\": f\"Bearer {API_KEY}\"},\n    files={\"file\": open(\"drawing.pdf\", \"rb\")},\n    data={\"name\": \"My Drawing\", \"trigger_ai_review\": \"true\"},\n)\ndrawing = response.json()\ndrawing_id = drawing[\"id\"]\nreview_id = drawing[\"review_id\"]\n\n# 2. Poll for results (reviews typically complete in 1-3 minutes)\nwhile True:\n    response = requests.get(\n        f\"https://www.clearhandoff.com/api/v1/drawings/{drawing_id}/ai-review/{review_id}/\",\n        headers={\"Authorization\": f\"Bearer {API_KEY}\"},\n    )\n    result = response.json()\n    if result[\"status\"] in (\"completed\", \"failed\"):\n        print(result)\n        break\n    time.sleep(5)\n```\n\n### curl\n\nUpload and start the review — the response includes the drawing's `id` and a `review_id`:\n\n```bash\ncurl -X POST https://www.clearhandoff.com/api/v1/drawings/ \\\n  -H \"Authorization: Bearer ch_YOUR_KEY\" \\\n  -F \"file=@drawing.pdf\" \\\n  -F \"name=My Drawing\" \\\n  -F \"trigger_ai_review=true\"\n```\n\nPoll until `status` is `\"completed\"` (reviews typically complete in 1-3 minutes); the response then includes findings, summary, and recommendation:\n\n```bash\ncurl https://www.clearhandoff.com/api/v1/drawings/DRAWING_ID/ai-review/REVIEW_ID/ \\\n  -H \"Authorization: Bearer ch_YOUR_KEY\"\n```"},{"name":"Drawings","description":"Upload, list, and retrieve drawings"},{"name":"Comments","description":"List, resolve, and reopen comments"},{"name":"Approvals","description":"List approval decisions (AI and human)"},{"name":"Downloads","description":"Download files that were originally uploaded (PDFs and DXFs) and annotated PDFs"},{"name":"AI Reviews","description":"Trigger and poll AI reviews"},{"name":"Account","description":"Credit balance and account information"},{"name":"Error handling","x-traitTag":true,"description":"Every error response uses the envelope `{\"error\": \"human-readable message\", \"error_code\": \"stable_code\"}`. We recommend branching on `error_code` and never on `error`, because its wording may change. Codes by area:\n\n- **Uploads**: `missing_file`, `invalid_file_type`, `file_too_large` (413), `trigger_ai_review_required`, `invalid_pdf`, `seat_limit_exceeded` (403), `drawing_limit` (403)\n- **Version lookup**: `version_not_found`, `invalid_version`, `no_dxf_original`\n- **DXF conversion**: `conversion_pending`, `conversion_failed` (409 from PDF consumers until the version's PDF exists; also the `error_code` of an AI review that failed because its DXF could not be converted)\n- **AI credits (402)**: `trial_expired`, `no_plan`, `upgrade_required`, `blocked`, `cap_reached`\n- **AI reviews**: `blank_custom_instructions`, `custom_instructions_too_long`, `conflicting_instructions`, `celery_unavailable` (503), `no_subscription` (403)\n- **Batch**: `missing_drawings`, `invalid_drawings`, `empty_drawings`, `batch_too_large`, `invalid_drawing_item`, `missing_drawing_id`, `invalid_drawing_id`\n- **Pen**: `missing_question`, `question_too_long`, `pen_cap_reached`, `no_ai_review`, `ambiguous_target`, `invalid_page`, `page_out_of_range`, `missing_pin`, `invalid_pin`, `invalid_parent_comment_id`, `review_not_found`, `comment_not_found`, `celery_unavailable` (503)\n\nNew codes may be added and will be documented here."}]}