Jobs
Creates a new compliance job for the given project + document set, starts the ComplianceJobWorkflow on Temporal, and returns the job_id immediately. The job runs asynchronously; poll GET /jobs/{job_id} or stream GET /jobs/{job_id}/events for progress.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
accept-language?string|nullapplication/json- body
Body for POST /v1/jobs — create a new compliance check job.
Examples: { "project_id": "550e8400-e29b-41d4-a716-446655440000", "document_ids": ["6ba7b810-9dad-11d1-80b4-00c04fd430c8"], "query": "مشروع مكتب 5 طوابق في الرياض", "query_scope": { "building_type": "office", "stories": 5, "occupancies": ["occ:B"] }, "region": "RUH", "codes": ["801"] }
codes*array<string>Multi-code platform input (Phase 5 / Phase 7 contract): the list of design codes to evaluate this job against. Each entry is a code identifier matching compliance.code_editions.code (e.g. '801' for SBC 801 Fire Code, '201' for SBC 201 General Building). At least one code is required. The caller must be entitled to every code in this list (platform codes are visible to all subscribers; tenant BYO codes are visible only to the owning tenant — see Phase 4). Verdicts in the response will be tagged with code_id + code_edition_id so the workbench can group them by code.
1 <= itemsconfirm_classification?Confirm ClassificationReserved. Recorded with the job but not acted on: the workflow never pauses for confirmation, whatever the value. The building's classification is decided by the platform (founder ruling 2026-09-24: no manual review step).
truedocument_ids*array<>One or more UUIDs of projects.project_documents rows to include in this job. Each document must belong to the given project_id. At least one document is required.
1 <= itemsplot_key?|Billing project key — the survey decision number (رقم القرار المساحي) for this plot. Identifies the project for the project-as-unit charge: a re-upload to the same plot attaches to the existing (already-charged) project and reruns are free. When omitted the server falls back to 'project:{project_id}'. NOT read from the drawing — it comes from the project settings the user enters.
project_id*Project IdUUID of the projects.projects row this job belongs to.
uuidquery*QueryFree-text description of the project scope in Arabic or English. Used by ScopeClassificationActivity to determine occupancy class and building type. Example: 'مشروع مكتب 5 طوابق في الرياض، استخدام تجاري مختلط'.
10 <= length <= 2000query_scope?|Structured scope for the job. REQUIRED key: occupancies, a non-empty list of SBC 201 occupancy ids such as 'occ:A-1', 'R-2' or 'B', each a division or a group without divisions (B, E, M, U); a bare group with divisions such as 'A' or 'R' is refused with the divisions to choose from. The declared occupancy is the building's occupancy for rule selection (founder ruling D6a: no analysis runs on a guessed one). Optional keys: building_type (str), stories (int), total_area_sqm (float), height_m (float). NOTE: height_m and total_area_sqm are not hints only — they are carried into evaluation as USER_STATED facts on the building (building.height_m, building.gross_floor_area_sqm), so rules such as the SBC high-rise threshold return a real PASS/FAIL instead of NOT_APPLICABLE. A value that is absent or not a positive number is ignored, and those rules abstain as before. NOTE: For region, prefer the explicit region field below — it's machine-checkable and feeds directly into the wind-load + temperature rule applicability filters.
region?|Optional KSA region code where the building is located. When set, scope_classification trusts this value and skips region inference from drawings. Wind-load + temperature/humidity rules in SBC are region-specific so explicit input reduces classification risk. Accepted: RUH (Riyadh), JED (Jeddah), DMM (Dammam), MEC (Makkah), MED (Madinah), TUU (Tabuk), AHB (Abha/Asir). Other ISO codes accepted but trigger a warning.
Job created.
application/json- response
Response for GET /v1/jobs/{job_id} and POST /v1/jobs.
The response shape is the same for both endpoints. On POST, status='pending' and all verdict/element fields are null. On GET after completion, the full verdict + element sets are populated.
Examples: # POST /jobs response (pending) { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "pending", "estimated_cost_credits": 12.5, "eta_seconds": 90 }
# GET /jobs/{id} response (completed){ "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "verdict_summary": {"total": 34, "pass_count": 28, ...}, "verdicts": [...], "elements": [...]}document_coverage?array<>Per-document result for the files in this job: whether each was READ, and when it was not, why and what would fix it. A document missing from this list was not assessed, which is different from being read.
elements?array<>|Visual elements extracted from the drawings. Null until job status='completed'.
error_code?|Machine-readable error code. Only present when status='failed'.
error_message_ar?|Arabic error message. Only present when status='failed'.
error_message_en?|English error message. Only present when status='failed'.
estimated_cost_credits?|Estimated credit cost for this job. Set at job creation based on document count and rule set size. Null until scope classification completes.
eta_seconds?|Estimated seconds until completion. Null for completed/failed/cancelled jobs.
job_id*Job IdUUID of the compliance job.
uuidresult_pdf_url?|S3 presigned URL to download the verdict PDF. Valid for 15 minutes after generation. Null until job status='completed'. Use GET /v1/jobs/{job_id}/result.pdf to get a fresh URL.
status*JobStatusCurrent lifecycle status of the job.
"pending""running""completed""failed""cancelled""expired"verdict_summary?|Aggregate verdict counts. Null until job status='completed'.
verdicts?array<>|Per-rule compliance verdicts. Null until job status='completed'. Each entry is the latest-version verdict per rule_id.
verdicts_inline_count?|Number of verdict rows in the inline verdicts slice. Present only when verdicts_truncated is True.
verdicts_truncated?Verdicts TruncatedTrue when the inline verdicts list is a bounded slice of the full result set. Use verdicts_url to retrieve all verdicts via pagination.
falseverdicts_url?|URL to the paginated verdict list for this job (GET /v1/jobs/{job_id}/verdicts). Present when status='completed'. Use this endpoint rather than the inline verdicts field for large jobs.
curl -X POST 'https://api.aisc.sa/v1/jobs' \ -H 'Content-Type: application/json' \ -d '{ "codes": [ "801" ], "document_ids": [ "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ], "project_id": "550e8400-e29b-41d4-a716-446655440000", "query": "مشروع مكتب 5 طوابق في الرياض، استخدام تجاري مختلط"}'{ "document_coverage": [ { "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "filename": "A-101-GROUND-FLOOR.dwg", "layer_convention": "iso_13567", "matched_count": 0, "onboarding_pairs": [ { "element_code": "G25", "label": "Wall" } ], "reason": "string", "total_entities": 0, "unread": false } ], "elements": [ { "attributes": { "area_sqm": 45.2, "width_mm": 4500 }, "bbox": { "height": 200, "width": 80, "x": 120.5, "y": 340.2 }, "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "element_type": "room", "label_ar": "ممر هروب", "label_en": "Egress corridor", "page": 3 } ], "error_code": "DOCUMENT_PARSE_FAILED", "error_message_ar": "فشل تحليل ملف PDF: تنسيق غير مدعوم", "error_message_en": "Document parse failed: unsupported format", "estimated_cost_credits": 12.5, "eta_seconds": 90, "job_id": "550e8400-e29b-41d4-a716-446655440000", "result_pdf_url": "https://s3.eu-central-1.amazonaws.com/emtithal-...?X-Amz-Expires=900", "status": "pending", "verdict_summary": { "conformity_form": { "blocking": [ "COVERAGE_SHORT", "NEEDS_REVIEW" ], "emittable": true, "rules_examined": 2886, "rules_in_force": 2886 }, "fail_count": 4, "needs_review_count": 2, "not_applicable_count": 0, "not_evaluable_count": 0, "overridden_count": 0, "pass_count": 28, "rules_examined": 34, "rules_in_force": 7687, "rules_withheld": 0, "total": 34 }, "verdicts": [ { "citations": [ {} ], "cited_drawing_element_id": "1aea6d40-086c-4933-ba23-c70ad6166893", "cited_rule_quote": "لا يجب أن يقل عرض الممر عن ١١٢٠ ملم", "code_edition_id": "4a5f38c5-02cb-48ac-ba22-00d46bdf8402", "code_id": "801", "confidence": 0.92, "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "justification_ar": "عرض الممر 1100 ملم أقل من الحد الأدنى المطلوب 1120 ملم", "justification_en": "Corridor width 1100mm is below the required minimum of 1120mm", "rule_id": "SBC301-1005.1", "verdict": "FAIL", "verdict_id": "a8098c1a-f86e-11da-bd1a-00112444be1e", "verdict_raw": "FAIL", "version": 1 } ], "verdicts_inline_count": 0, "verdicts_truncated": false, "verdicts_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/verdicts"}Returns the current state of a compliance job. When status='completed', the response includes all verdicts and drawing elements.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidaccept-language?string|nullJob found.
application/json- response
Response for GET /v1/jobs/{job_id} and POST /v1/jobs.
The response shape is the same for both endpoints. On POST, status='pending' and all verdict/element fields are null. On GET after completion, the full verdict + element sets are populated.
Examples: # POST /jobs response (pending) { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "pending", "estimated_cost_credits": 12.5, "eta_seconds": 90 }
# GET /jobs/{id} response (completed){ "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "verdict_summary": {"total": 34, "pass_count": 28, ...}, "verdicts": [...], "elements": [...]}document_coverage?array<>Per-document result for the files in this job: whether each was READ, and when it was not, why and what would fix it. A document missing from this list was not assessed, which is different from being read.
elements?array<>|Visual elements extracted from the drawings. Null until job status='completed'.
error_code?|Machine-readable error code. Only present when status='failed'.
error_message_ar?|Arabic error message. Only present when status='failed'.
error_message_en?|English error message. Only present when status='failed'.
estimated_cost_credits?|Estimated credit cost for this job. Set at job creation based on document count and rule set size. Null until scope classification completes.
eta_seconds?|Estimated seconds until completion. Null for completed/failed/cancelled jobs.
job_id*Job IdUUID of the compliance job.
uuidresult_pdf_url?|S3 presigned URL to download the verdict PDF. Valid for 15 minutes after generation. Null until job status='completed'. Use GET /v1/jobs/{job_id}/result.pdf to get a fresh URL.
status*JobStatusCurrent lifecycle status of the job.
"pending""running""completed""failed""cancelled""expired"verdict_summary?|Aggregate verdict counts. Null until job status='completed'.
verdicts?array<>|Per-rule compliance verdicts. Null until job status='completed'. Each entry is the latest-version verdict per rule_id.
verdicts_inline_count?|Number of verdict rows in the inline verdicts slice. Present only when verdicts_truncated is True.
verdicts_truncated?Verdicts TruncatedTrue when the inline verdicts list is a bounded slice of the full result set. Use verdicts_url to retrieve all verdicts via pagination.
falseverdicts_url?|URL to the paginated verdict list for this job (GET /v1/jobs/{job_id}/verdicts). Present when status='completed'. Use this endpoint rather than the inline verdicts field for large jobs.
curl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08'{ "document_coverage": [ { "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "filename": "A-101-GROUND-FLOOR.dwg", "layer_convention": "iso_13567", "matched_count": 0, "onboarding_pairs": [ { "element_code": "G25", "label": "Wall" } ], "reason": "string", "total_entities": 0, "unread": false } ], "elements": [ { "attributes": { "area_sqm": 45.2, "width_mm": 4500 }, "bbox": { "height": 200, "width": 80, "x": 120.5, "y": 340.2 }, "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "element_type": "room", "label_ar": "ممر هروب", "label_en": "Egress corridor", "page": 3 } ], "error_code": "DOCUMENT_PARSE_FAILED", "error_message_ar": "فشل تحليل ملف PDF: تنسيق غير مدعوم", "error_message_en": "Document parse failed: unsupported format", "estimated_cost_credits": 12.5, "eta_seconds": 90, "job_id": "550e8400-e29b-41d4-a716-446655440000", "result_pdf_url": "https://s3.eu-central-1.amazonaws.com/emtithal-...?X-Amz-Expires=900", "status": "pending", "verdict_summary": { "conformity_form": { "blocking": [ "COVERAGE_SHORT", "NEEDS_REVIEW" ], "emittable": true, "rules_examined": 2886, "rules_in_force": 2886 }, "fail_count": 4, "needs_review_count": 2, "not_applicable_count": 0, "not_evaluable_count": 0, "overridden_count": 0, "pass_count": 28, "rules_examined": 34, "rules_in_force": 7687, "rules_withheld": 0, "total": 34 }, "verdicts": [ { "citations": [ {} ], "cited_drawing_element_id": "1aea6d40-086c-4933-ba23-c70ad6166893", "cited_rule_quote": "لا يجب أن يقل عرض الممر عن ١١٢٠ ملم", "code_edition_id": "4a5f38c5-02cb-48ac-ba22-00d46bdf8402", "code_id": "801", "confidence": 0.92, "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "justification_ar": "عرض الممر 1100 ملم أقل من الحد الأدنى المطلوب 1120 ملم", "justification_en": "Corridor width 1100mm is below the required minimum of 1120mm", "rule_id": "SBC301-1005.1", "verdict": "FAIL", "verdict_id": "a8098c1a-f86e-11da-bd1a-00112444be1e", "verdict_raw": "FAIL", "version": 1 } ], "verdicts_inline_count": 0, "verdicts_truncated": false, "verdicts_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/verdicts"}Sends a cancellation signal to the Temporal ComplianceJobWorkflow. Licensed_engineer-only.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidaccept-language?string|nullCancellation signal sent.
response?unknownapplication/jsoncurl -X POST 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/cancel'nullAnswers the question GET /jobs/{job_id} already asks. When a drawing is unread, document_coverage[].onboarding_pairs lists the (element_code, label) pairs that would make it readable; this records one such answer for THIS TENANT ONLY.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuiddocument_id*Document Iduuidapplication/json- body
Body for POST /v1/jobs/{job_id}/documents/{document_id}/codes.
The REPLY to a question we already asked. DocumentCoverage reports an
unread drawing and lists onboarding_pairs -- the (element_code, label)
pairs that would make it readable -- and until now nothing could accept an
answer, so the remedy we printed had no button (RFC-011).
The convention is NOT in this body. It is read from the document's own read-status row, which is what the reader recorded when it failed: a customer retyping 'iso_13567' could silently file the meaning under a convention no drawing uses, where it would never be found again.
element_code*Element CodeThe layer code being explained, VERBATIM as we reported it: 'G25'. Must be one of the onboarding_pairs we listed for this document.
length <= 40element_type*ElementTypeWhat that code means in this firm's drawings. Constrained to the vocabulary the rules are written against -- unlike the free-text corrected_element_type on an override, which reports a class we may be MISSING. Here a value outside the vocabulary cannot help: it would retype every matching layer in every future job to something no rule can read, and SAFE-1 could not detect it, because a wrong reading is exactly as corroborated as a right one.
"room""door""window""exit""stair""corridor""ramp""shaft""elevator""mezzanine""fire_compartment""occupancy_zone""egress_path""wall""column""beam""slab""foundation""reinforcement""steel_connection""fire_alarm""smoke_detector""fire_extinguisher""sprinkler""standpipe""fire_pump""exit_sign""smoke_vent""emergency_light""travel_distance""common_path""fire_zone""circuit""earth_electrode""conductor""distribution_panel""socket_outlet""luminaire""conduit""transformer""air_handling_unit""chiller""fan""damper""diffuser""boiler""water_heater""trap""plumbing_fixture""backflow_preventer""floor_drain""cleanout""gas_appliance""gas_regulator""gas_meter""shutoff_valve""vent_terminal""footing""anchor""brace""pile""retaining_wall""expansion_joint""pool""diving_board""pool_drain""pool_barrier""insulation""glazing_unit""energy_meter""solar_panel""duct""pipe""equipment""building""floor""site""other""occupancy_area""unknown"label?LabelThe free-text tail we reported alongside the code ('Wall'). Part of the key, because one code covers several types: G25 is Wall AND Door AND Window depending on it.
length <= 120""Meaning recorded. Applies to the tenant's FUTURE jobs.
response?unknownapplication/jsoncurl -X POST 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/documents/497f6eca-6276-4993-bfeb-53cbbbba6f08/codes' \ -H 'Content-Type: application/json' \ -d '{ "element_code": "G25", "element_type": "wall"}'nullADR-0025 D5 / PRD-007 finding card: returns a short-TTL presigned URL for the page image the ingest pipeline produced — PNG raster for PDF/legacy documents, or the emt-e6s server-side SVG render for DXF/DWG documents (probed in that order; whichever exists is presigned). IFC uploads still have no page image (no render path built for them) and return 404 PREVIEW.PAGE_IMAGE_NOT_FOUND, same as a page out of range or a withheld/failed render.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuiddocument_id*Document Iduuidpage*Page1-based page number.
1 <= value <= 10000Presigned page-image URL.
application/json- response
A short-TTL URL for one rasterized document page.
expires_in_seconds*Expires In SecondsURL validity window.
page*Page1-based page number.
url*UrlPresigned GET URL for the page PNG (short TTL).
curl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/documents/497f6eca-6276-4993-bfeb-53cbbbba6f08/pages/1/image'{ "expires_in_seconds": 300, "page": 3, "url": "string"}Server-Sent Events stream of compliance job progress. Reconnect with Last-Event-ID to resume from the last received sequence.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidlast-event-id?string|nullaccept-language?string|nullSSE stream of job progress events.
response?unknowncurl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/events'nullGenerates a BCF 3.0 (BIM Collaboration Format) zip archive from the job's FAIL verdicts and every verdict that needs an engineer (NEEDS_REVIEW, CONFLICT, NOT_APPLICABLE_NO_ELEMENT_FOUND), read as presented verdicts. Each qualifying verdict becomes a BCF topic with title, status, rule snippet, and an element viewpoint bounding box where available. The archive is empty (but valid) when the job has none. Read-only — queries verdicts, writes nothing.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidaccept-language?string|nullBCF 3.0 zip archive.
response?unknowncurl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/issues.bcf'nullLicensed engineer overrides a compliance verdict. Persisted to projects.job_overrides AND signals the Temporal workflow.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidaccept-language?string|nullapplication/json- body
Body for POST /v1/jobs/{job_id}/override — reviewer verdict override.
Override is a first-class audit event. Justification is required; there are no rubber-stamp overrides. The licensed engineer must provide the reason in the regulatory language (Arabic preferred; English accepted).
Examples: { "rule_id": "SBC301-1005.1", "original_verdict": "FAIL", "new_verdict": "PASS", "justification": "المسافة المقيسة على الرسم صحيحة؛ الخطأ في التعرف البصري للحدود" }
corrected_element_type?|OPTIONAL. What the reviewer says the element actually is, when disputed_element_id is supplied. Free text on purpose: a value outside the current vocabulary is evidence that the vocabulary is missing a class, and rejecting it would discard the report that reveals the gap.
disputed_element_id?|OPTIONAL. The drawing element the reviewer says was read wrongly. Supply it only when the override disputes our READING of the drawing rather than the rule: the justification example above is exactly that case, and today it is recoverable only by reading the Arabic back with a model. Omit it for a rule disagreement; a value invented to fill the field is a fabricated correction.
justification*JustificationRequired free-text justification for the override. Arabic is preferred (regulatory language). Minimum 20 characters. This text is preserved in projects.job_overrides for 7-year audit retention.
20 <= length <= 2000new_verdict*VerdictValueThe reviewer's replacement verdict. OVERRIDDEN is set automatically by the system; supply PASS, FAIL, or NEEDS_REVIEW.
"PASS""FAIL""NEEDS_REVIEW""OVERRIDDEN""NOT_APPLICABLE"original_verdict*VerdictValueThe reviewer's replacement verdict. OVERRIDDEN is set automatically by the system; supply PASS, FAIL, or NEEDS_REVIEW.
"PASS""FAIL""NEEDS_REVIEW""OVERRIDDEN""NOT_APPLICABLE"rule_id*Rule IdRule identifier whose verdict is being overridden. Must match a rule_id in compliance.rules. Accepts live corpus IDs such as 'SBC-801-303.7-b77eaf76' (slash-separated taxonomy paths) and future edition-qualified IDs of the form '{code}:{edition}:{section}'.
^[A-Za-z0-9._/:\-]{1,120}$Override recorded and workflow signalled.
response?unknownapplication/jsoncurl -X POST 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/override' \ -H 'Content-Type: application/json' \ -d '{ "justification": "المسافة المقيسة على الرسم صحيحة؛ الخطأ في التعرف البصري للحدود", "new_verdict": "PASS", "original_verdict": "FAIL", "rule_id": "SBC-801-303.7-b77eaf76"}'nullReturns an S3 presigned URL for the compliance verdict PDF. Valid for 15 minutes.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidaccept-language?string|nullPresigned URL + expiry.
response?unknownapplication/jsoncurl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/result.pdf'nullAUD-048: returns ALL element-grained verdict rows for a single rule_id at the current (MAX) version — one row per cited_drawing_element_id. No rollup is applied here; both the FAIL row for door-A and the PASS row for door-B are returned so the workbench can deep-link each element to its position on the canvas. Paginated.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidrule_id*Rule Idverdict?|Filter by presented verdict. Must be an exact PresentedVerdict (PASS / FAIL / NEEDS_REVIEW / NOT_APPLICABLE / NOT_APPLICABLE_NO_ELEMENT_FOUND / NOT_APPLICABLE_SCOPE_UNCONFIRMED / CONFLICT). Invalid values return 422.
limit?LimitPage size (1-500).
1 <= value <= 500200offset?OffsetZero-based offset.
0 <= value0accept-language?string|nullPer-element verdicts for the rule.
response?unknownapplication/jsoncurl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/rules/string/verdicts'nullReturns the compliance verdicts for a job as a paginated flat list. AUD-048: verdicts are worst-of-rolled-up per rule_id — a FAIL on any element surfaces as FAIL at rule grain, never masked by a PASS row. AUD-053: results are paginated (limit/offset); total_count is returned so the frontend can render a page indicator. Each verdict carries the multi-code platform fields (code_id, code_edition_id, element_id, citations) per docs/api/verdict-shape.md.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidcode_id?|Filter by design-code identifier (e.g. '801', '201').
element_type?|Filter by drawing element type. Must be an exact ElementType value (lowercase, e.g. 'door', 'corridor', 'room'). Invalid values return 422.
verdict_state?|Filter by presented verdict. Must be an exact PresentedVerdict (PASS / FAIL / NEEDS_REVIEW / NOT_APPLICABLE / NOT_APPLICABLE_NO_ELEMENT_FOUND / NOT_APPLICABLE_SCOPE_UNCONFIRMED / CONFLICT). Invalid values return 422.
limit?LimitMaximum number of verdicts to return per page (1-500). Default: 200.
1 <= value <= 500200offset?OffsetZero-based offset for pagination.
0 <= value0accept-language?string|nullList of verdicts.
application/json- response
AUD-061: typed response for GET /jobs/{job_id}/verdicts.
verdicts is a list of raw DB rows (each is a dict keyed per
docs/api/verdict-shape.md). Using list[Any] avoids coupling the route
layer to a DB-shape Pydantic model that would need to track every column
rename — the CTO can generate types from the committed openapi.json schema.
filters*AUD-061: filters applied to the verdict list query.
job_id*Job Iduuidtotal_count*Total Countverdicts*array<unknown>curl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/verdicts'{ "filters": { "code_id": "string", "element_type": "string", "limit": 0, "offset": 0, "verdict_state": "string" }, "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", "total_count": 0, "verdicts": [ null ]}ADR-0025 D5 / PRD-007 Evidence Overlay: resolve one verdict to the drawing evidence the frontend needs for lineage click-through — the cited element's sheet_id, page and bbox plus the rule citation text. element is null for scope-level verdicts that cite no drawing element.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
job_id*Job Iduuidverdict_id*Verdict IduuidResolved citation with element evidence (or element: null).
application/json- response
Everything the workstation needs to render a verdict's evidence lineage.
cited_rule_quote?|Exact rule text quoted by the evaluator.
element?|The cited drawing element with sheet/page/bbox; null for scope-level verdicts that cite no specific element.
job_id*Job IdCompliance job UUID.
uuidjustification_ar?|Arabic justification.
justification_en?|English justification.
rule_id*Rule IdCited rule id.
verdict*VerdictThe presented verdict (spec B REQ-B-07): PASS / FAIL / NEEDS_REVIEW / NOT_APPLICABLE / NOT_APPLICABLE_NO_ELEMENT_FOUND / NOT_APPLICABLE_SCOPE_UNCONFIRMED / CONFLICT.
verdict_id*Verdict IdVerdict row UUID.
uuidverdict_raw?|The stored verdict, for audit. Never the value to act on: read verdict.
curl -X GET 'https://api.aisc.sa/v1/jobs/497f6eca-6276-4993-bfeb-53cbbbba6f08/verdicts/497f6eca-6276-4993-bfeb-53cbbbba6f08/citation'{ "cited_rule_quote": "string", "element": { "bbox": { "x_max": 12.4, "x_min": 10, "y_max": 6, "y_min": 4.5 }, "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "element_type": "corridor", "label_ar": "string", "label_en": "string", "page": 3, "sheet_id": "90db66ec-6a25-4d34-a966-efba30db59e0" }, "job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f", "justification_ar": "string", "justification_en": "string", "rule_id": "801-1020.2", "verdict": "FAIL", "verdict_id": "9e06fb72-25b2-4aaa-889d-bb1391af7030", "verdict_raw": "FAIL"}Returns recent compliance jobs for the given project, ordered by created_at descending. Paginated via limit and offset.
BearerAuthAuthorizationBearer <token>JWT access token. Obtain via POST /v1/auth/login.
project_id*Project Iduuidlimit?Limit1 <= value <= 10020offset?Offset0 <= value0accept-language?string|nullList of jobs.
application/json- response
[index: integer]?Response for GET /v1/jobs/{job_id} and POST /v1/jobs.
The response shape is the same for both endpoints. On POST, status='pending' and all verdict/element fields are null. On GET after completion, the full verdict + element sets are populated.
Examples: # POST /jobs response (pending) { "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "pending", "estimated_cost_credits": 12.5, "eta_seconds": 90 }
# GET /jobs/{id} response (completed){ "job_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "verdict_summary": {"total": 34, "pass_count": 28, ...}, "verdicts": [...], "elements": [...]}curl -X GET 'https://api.aisc.sa/v1/projects/497f6eca-6276-4993-bfeb-53cbbbba6f08/jobs'[ { "document_coverage": [ { "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "filename": "A-101-GROUND-FLOOR.dwg", "layer_convention": "iso_13567", "matched_count": 0, "onboarding_pairs": [ { "element_code": "G25", "label": "Wall" } ], "reason": "string", "total_entities": 0, "unread": false } ], "elements": [ { "attributes": { "area_sqm": 45.2, "width_mm": 4500 }, "bbox": { "height": 200, "width": 80, "x": 120.5, "y": 340.2 }, "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6", "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "element_type": "room", "label_ar": "ممر هروب", "label_en": "Egress corridor", "page": 3 } ], "error_code": "DOCUMENT_PARSE_FAILED", "error_message_ar": "فشل تحليل ملف PDF: تنسيق غير مدعوم", "error_message_en": "Document parse failed: unsupported format", "estimated_cost_credits": 12.5, "eta_seconds": 90, "job_id": "550e8400-e29b-41d4-a716-446655440000", "result_pdf_url": "https://s3.eu-central-1.amazonaws.com/emtithal-...?X-Amz-Expires=900", "status": "pending", "verdict_summary": { "conformity_form": { "blocking": [ "COVERAGE_SHORT", "NEEDS_REVIEW" ], "emittable": true, "rules_examined": 2886, "rules_in_force": 2886 }, "fail_count": 4, "needs_review_count": 2, "not_applicable_count": 0, "not_evaluable_count": 0, "overridden_count": 0, "pass_count": 28, "rules_examined": 34, "rules_in_force": 7687, "rules_withheld": 0, "total": 34 }, "verdicts": [ { "citations": [ {} ], "cited_drawing_element_id": "1aea6d40-086c-4933-ba23-c70ad6166893", "cited_rule_quote": "لا يجب أن يقل عرض الممر عن ١١٢٠ ملم", "code_edition_id": "4a5f38c5-02cb-48ac-ba22-00d46bdf8402", "code_id": "801", "confidence": 0.92, "element_id": "98026c2a-b90c-4843-8adc-9992613765c9", "justification_ar": "عرض الممر 1100 ملم أقل من الحد الأدنى المطلوب 1120 ملم", "justification_en": "Corridor width 1100mm is below the required minimum of 1120mm", "rule_id": "SBC301-1005.1", "verdict": "FAIL", "verdict_id": "a8098c1a-f86e-11da-bd1a-00112444be1e", "verdict_raw": "FAIL", "version": 1 } ], "verdicts_inline_count": 0, "verdicts_truncated": false, "verdicts_url": "/v1/jobs/550e8400-e29b-41d4-a716-446655440000/verdicts" }]