Developer API Reference
Everything you need to drive the QIYAS Compliance Mapper without using the UI. All backend features are exposed through same-origin proxy routes on qiyas.velents.ai: /api/db/* (PostgREST), /api/storage/* (Storage), and /api/analyze-file (AI analysis).
1. Base URL & auth
All endpoints are served from this app's own domain. The server injects the publishable (anon) key on your behalf — you do not send any auth headers. RLS still applies exactly as if you called the database directly.
BASE = https://qiyas.velents.ai
HEADERS = { "Content-Type": "application/json" } // no apikey, no AuthorizationTables in this POC use permissive RLS, so any caller can read/write standards, clauses, projects, files, and mappings. For production use, tighten RLS on the backend.
2. Standards & clauses
Discover available standards and the clauses they contain.
GET https://qiyas.velents.ai/api/db/standards?select=*&order=codeResponse:
[
{ "id": "uuid", "code": "5.11.1", "title_ar": "..." , "created_at": "..." }
]List clauses for a standard, ordered by their canonical sort order:
GET https://qiyas.velents.ai/api/db/clauses?standard_id=eq.<standardId>&select=*&order=sort_orderEach clause has: id, number (e.g. 5.11.1.3), title_ar, text_ar, special_rule_key (nullable), sort_order.
3. Projects
A project ties an entity + cycle to one standard. Create one:
POST https://qiyas.velents.ai/api/db/projects
Headers: Content-Type: application/json, Prefer: return=representation
Body:
{
"standard_code": "5.11.1",
"entity_name": "وكالة التحول الرقمي", // optional, has a default
"cycle": "2026" // optional, has a default
}List recent projects:
GET https://qiyas.velents.ai/api/db/projects?select=*&order=created_at.desc&limit=50Fetch a single project:
GET https://qiyas.velents.ai/api/db/projects?id=eq.<projectId>&select=*Delete a project (cascades to its files and mappings if FK is set):
DELETE https://qiyas.velents.ai/api/db/projects?id=eq.<projectId>4. Files (evidence upload)
Uploading evidence is a two-step flow: upload the binary to the evidence storage bucket, then insert a row in files pointing at the stored path.
4.1 Upload to Storage
POST https://qiyas.velents.ai/api/storage/object/evidence/<projectId>/<uuid>-<filename>
Headers:
Content-Type: <mime> // e.g. application/pdf, image/png
Body: <binary file content>The storage path you used is what you save in files.storage_path.
4.2 Create the files row
POST https://qiyas.velents.ai/api/db/files
Headers: Content-Type: application/json, Prefer: return=representation
Body:
{
"project_id": "uuid",
"name": "policy.pdf",
"mime": "application/pdf",
"size": 123456,
"storage_path": "<projectId>/<uuid>-policy.pdf",
"status": "uploaded"
}4.3 status lifecycle
uploaded -> initial state, ready to analyze
analyzing -> analyze-file (suggest mode) is running
suggested -> AI proposed a clause; awaiting confirmation
confirmed -> verdict mode ran; mapping row written
error -> see files.error_messageUseful read:
GET https://qiyas.velents.ai/api/db/files?project_id=eq.<projectId>&select=*&order=created_at.desc5. analyze-file
Single endpoint, two modes. No auth headers required.
POST https://qiyas.velents.ai/api/analyze-file5.1 Mode: suggest
Ask the AI which clause the file most likely proves.
Request:
{ "mode": "suggest", "fileId": "<uuid>" }
Response 200:
{
"suggestion": {
"clause_number": "5.11.1.3",
"confidence": 0.87,
"reason_ar": "...",
"alternatives": ["5.11.1.4", "5.11.2.1"]
},
"suggested_clause_id": "uuid | null"
}
Side effects on the files row:
status = "analyzing" then "suggested"
suggested_clause_id = matched clause uuid (or null)
suggestion_confidence = number 0..1
suggestion_alternatives = string[]5.2 Mode: verdict
Score a file against a specific clause and write the mapping.
Request:
{ "mode": "verdict", "fileId": "<uuid>", "clauseId": "<uuid>" }
Response 200:
{
"verdict": {
"verdict": "accepted" | "accepted_with_reservations" | "rejected",
"acceptance_score": 0..100,
"reason_ar": "...",
"quote_ar": "..." | null,
"recommendations": [
{
"severity": "must_fix" | "nice_to_have",
"priority": 1, // 1 = highest
"action_ar": "...", // full sentence, ≥ 12 words
"example_ar": "..." // concrete snippet to paste in
}
],
"missing_artifacts": ["..."]
}
}Score banding
accepted -> acceptance_score >= 85
accepted_with_reservations -> 40 <= acceptance_score <= 84
rejected -> acceptance_score < 40Side effects
UPSERT public.mappings (onConflict: file_id, clause_id) with:
project_id, clause_id, file_id,
verdict, verdict_reason_ar, quote_ar,
recommendations, missing_artifacts, acceptance_score
UPDATE public.files SET status = "confirmed" WHERE id = fileIdSpecial clause rules
When a clause has special_rule_key, the model is given an extra constraint:
sla_plus_three_screenshots
Requires an approved SLA document + 3 ITSM screenshots
(SLA definition, linked ticket, compliance report).
Partial coverage -> accepted_with_reservations.
policy_plus_dissemination
Requires an approved policy + evidence of dissemination
to staff. Either missing -> accepted_with_reservations.
quality_total_wording
Document must say "الجودة الشاملة" specifically.
If it only says "الجودة" -> accepted_with_reservations
with a recommendation to fix the wording.Errors
{ "error": "<message>" }
HTTP 429 -> AI rate limit
HTTP 402 -> AI credit / billing issue
HTTP 500 -> anything else (see message)6. Mappings (verdicts)
Each confirmed file-to-clause pair lives in mappings. Read all verdicts for a project:
GET https://qiyas.velents.ai/api/db/mappings
?project_id=eq.<projectId>
&select=*,clauses(*),files(*)
&order=created_at.descManually override the AI verdict (e.g. a human reviewer overrides to accepted):
PATCH https://qiyas.velents.ai/api/db/mappings?id=eq.<mappingId>
{
"verdict": "accepted",
"acceptance_score": 100,
"manual_override": true,
"override_reason": "Reviewed by compliance lead"
}Delete a mapping (un-link a file from a clause):
DELETE https://qiyas.velents.ai/api/db/mappings?id=eq.<mappingId>7. Change notifications
For headless integrations, poll the proxied REST endpoints to detect changes — no direct backend connection required:
// Poll every few seconds while a project is active
setInterval(async () => {
const files = await fetch(
`${BASE}/api/db/files?project_id=eq.${projectId}&select=*&order=created_at.desc`,
).then((r) => r.json());
// diff against previous snapshot and react to status changes
}, 4000);8. End-to-end recipe
One self-contained script — no SDK needed — that creates a project, uploads a PDF, runs both AI modes, and reads the resulting mapping. Everything targets https://qiyas.velents.ai.
import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const BASE = "https://qiyas.velents.ai";
const H = { "Content-Type": "application/json" };
// 1) Create a project
const project = await fetch(`${BASE}/api/db/projects`, {
method: "POST",
headers: { ...H, Prefer: "return=representation" },
body: JSON.stringify({ standard_code: "5.11.1", entity_name: "Acme" }),
}).then((r) => r.json()).then((rows) => rows[0]);
// 2) Upload a PDF to the evidence bucket
const bytes = await readFile("./policy.pdf");
const path = `${project.id}/${randomUUID()}-policy.pdf`;
await fetch(`${BASE}/api/storage/object/evidence/${path}`, {
method: "POST",
headers: { "Content-Type": "application/pdf" },
body: bytes,
});
// 3) Register the file row
const file = await fetch(`${BASE}/api/db/files`, {
method: "POST",
headers: { ...H, Prefer: "return=representation" },
body: JSON.stringify({
project_id: project.id,
name: "policy.pdf",
mime: "application/pdf",
size: bytes.byteLength,
storage_path: path,
status: "uploaded",
}),
}).then((r) => r.json()).then((rows) => rows[0]);
// 4) Ask the AI which clause this file proves
const suggest = await fetch(`${BASE}/api/analyze-file`, {
method: "POST",
headers: H,
body: JSON.stringify({ mode: "suggest", fileId: file.id }),
}).then((r) => r.json());
// 5) Score the file against that clause
const verdict = await fetch(`${BASE}/api/analyze-file`, {
method: "POST",
headers: H,
body: JSON.stringify({
mode: "verdict",
fileId: file.id,
clauseId: suggest.suggested_clause_id,
}),
}).then((r) => r.json());
console.log(verdict);
// { verdict: { verdict: "accepted_with_reservations", acceptance_score: 72, ... } }
// 6) Read the mapping that was written
const mappings = await fetch(
`${BASE}/api/db/mappings?project_id=eq.${project.id}` +
`&select=*,clauses(*),files(*)`,
).then((r) => r.json());
console.log(mappings);