{"openapi":"3.1.0","info":{"title":"Arka API","version":"1.0.0","summary":"Read your projects and push your own data into Arka.","description":"A REST API over your organisation's Arka data. Every response is JSON.\n\n**What it is for.** Your costs, receipts and payments already exist in an\naccounting system. Retyping them into Arka is what makes the margin and\ncash figures untrusted. Push them here and the project financials, the\nageing report and the cash position follow automatically.\n\n**Conventions**\n- Times are UTC ISO-8601. Dates are `YYYY-MM-DD`.\n- Money is returned as a **string** to preserve exact decimals. Send numbers.\n- A key acts for your whole organisation. It can never reach another tenant.\n- Anything that creates a record accepts an `Idempotency-Key` header; retrying\n  with the same value replays the first response instead of writing twice.\n\n**What the API deliberately will not do.** It cannot approve a parked change,\nmint a client-portal link, or alter another organisation's records. Editing a\nrecord through the API is subject to the same approval policy as editing it on\nscreen. A `202` means the change was recorded as a request, not applied.","contact":{"name":"Arka","url":"https://www.norvis.in/docs"}},"servers":[{"url":"https://www.norvis.in/api/v1","description":"Your organisation"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Discovery","description":"Confirm a key works and see what it can reach."},{"name":"Projects","description":"Read projects and their phases."},{"name":"Delivery","description":"Tasks and risks."},{"name":"Finance","description":"Costs, invoices and cash. The push-your-data-in surface."}],"paths":{"/":{"get":{"tags":["Discovery"],"operationId":"getIndex","summary":"Check a key and list what it can reach","description":"The cheapest way to confirm a key is live and which scopes it holds. Call this first when wiring up an integration.","responses":{"200":{"description":"The key is valid.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"tenant":{"type":"string","format":"uuid"},"your_scopes":{"type":"array","items":{"type":"string","enum":["read","write"]}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/projects":{"get":{"tags":["Projects"],"operationId":"listProjects","summary":"List your projects","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Your projects.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Project"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/projects/{projectId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Projects"],"operationId":"getProject","summary":"One project with its phases","responses":{"200":{"description":"The project.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Project"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/tasks":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Delivery"],"operationId":"listTasks","summary":"List a project's tasks","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}}],"responses":{"200":{"description":"Tasks.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Task"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Delivery"],"operationId":"createTask","summary":"Create a task","description":"Appended at the end of the top-level tasks. Needs the `write` scope.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string you generate. Resending the same value replays the first response instead of creating a second record: send one on every create from an automated sync.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskCreate"}}}},"responses":{"201":{"description":"Created.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Task"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ScopeRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/tasks/{taskId}":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"taskId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"patch":{"tags":["Delivery"],"operationId":"updateTask","summary":"Update a task","description":"Needs the `write` scope.\n\n**This can answer 202.** If your organisation requires approval for task\nedits, the change is recorded as a request and is NOT yet in effect. Treat\n202 as \"submitted\", never as \"saved\". The record still holds its old values\nuntil an approver signs off.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaskUpdate"}}}},"responses":{"200":{"description":"Applied.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Task"}}}}}},"202":{"description":"Sent for approval: not applied.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"pending_approval"},"message":{"type":"string"},"change_request_id":{"type":"string","format":"uuid"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ScopeRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/risks":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Delivery"],"operationId":"listRisks","summary":"List a project's risks","responses":{"200":{"description":"Risks.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Risk"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/invoices":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Finance"],"operationId":"listInvoices","summary":"List a project's invoices","responses":{"200":{"description":"Invoices.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/cost-entries":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Finance"],"operationId":"listCostEntries","summary":"List booked costs","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"since","in":"query","required":false,"description":"Only records dated on or after this day. Use it to sync incrementally.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Costs.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CostEntry"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Finance"],"operationId":"createCostEntry","summary":"Push a cost from your accounting system","description":"The main integration point. A purchase invoice, a payroll allocation, plant\nhire: anything already booked elsewhere. It feeds the project's forecast\ncost, margin and cost-code breakdown immediately.\n\nNeeds the `write` scope. Send an `Idempotency-Key` so a retried sync does\nnot book the cost twice.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string you generate. Resending the same value replays the first response instead of creating a second record: send one on every create from an automated sync.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CostEntryCreate"}}}},"responses":{"201":{"description":"Booked.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CostEntry"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ScopeRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/projects/{projectId}/settlements":{"parameters":[{"name":"projectId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Finance"],"operationId":"listSettlements","summary":"List cash that actually moved","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0}},{"name":"since","in":"query","required":false,"description":"Only records dated on or after this day. Use it to sync incrementally.","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"Settlements.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Settlement"}},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Finance"],"operationId":"createSettlement","summary":"Push a receipt or payment","description":"One movement of cash. Supply `schedule_id` to settle a planned line: the\nline's balance and status are recomputed by the database, so do not try to\nset them yourself. Omit it for money that was never scheduled; that is\noff-schedule cash and it still counts in the project's position.\n\nNeeds the `write` scope.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Any unique string you generate. Resending the same value replays the first response instead of creating a second record: send one on every create from an automated sync.","schema":{"type":"string","maxLength":200}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SettlementCreate"}}}},"responses":{"201":{"description":"Recorded.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Settlement"}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ScopeRequired"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"More than the line still owes. Resend with allow_overpayment: true if the excess is real.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"conflict"},"message":{"type":"string"},"outstanding":{"type":"number"}}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your API key from Settings → API keys. `Authorization: Bearer ark_live_…`. A key acts for the whole organisation, so treat it as a credential and revoke it the moment it leaks."}},"parameters":{},"responses":{"Unauthorized":{"description":"Missing, malformed, revoked or unknown key.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"unauthorized"},"message":{"type":"string"}}}}}},"ScopeRequired":{"description":"This key is read-only. Create one with write access.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"forbidden"},"message":{"type":"string"}}}}}},"NotFound":{"description":"No such record in your organisation. Deliberately identical to \"belongs to someone else\": confirming another tenant's id exists would itself leak.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"not_found"},"message":{"type":"string"}}}}}},"ValidationError":{"description":"The body did not validate. `details` names the offending fields.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"validation_error"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}}},"RateLimited":{"description":"Too many requests for this key. `Retry-After` says how long to wait. The per-minute ceiling is set per key in Settings.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","const":"rate_limited"},"message":{"type":"string"}}}}}}},"schemas":{"Project":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"code":{"type":"string","nullable":true},"status":{"type":"string"},"client_name":{"type":"string","nullable":true},"contract_value":{"type":"number","description":"Head contract value. Sent as a JSON number; returned as a STRING so no precision is lost in transit."},"start_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"target_completion":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true}}},"Task":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"wbs_code":{"type":"string","description":"Position in the work breakdown, e.g. \"4.1.2\"."},"title":{"type":"string"},"status":{"type":"string","enum":["not_started","in_progress","blocked","on_hold","done","cancelled"]},"priority":{"type":"string","enum":["low","medium","high","critical"]},"percent_complete":{"type":"number","minimum":0,"maximum":100},"planned_start":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"planned_end":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"duration_days":{"type":"integer","nullable":true}}},"TaskCreate":{"type":"object","required":["title"],"properties":{"title":{"type":"string","minLength":1,"maxLength":300},"priority":{"type":"string","enum":["low","medium","high","critical"],"default":"medium"},"planned_start":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"planned_end":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true}}},"TaskUpdate":{"type":"object","minProperties":1,"properties":{"status":{"type":"string","enum":["not_started","in_progress","blocked","on_hold","done","cancelled"]},"percent_complete":{"type":"number","minimum":0,"maximum":100},"planned_start":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"planned_end":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"priority":{"type":"string","enum":["low","medium","high","critical"]}},"description":"Setting status to `done` sets percent_complete to 100 for you."},"Risk":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"category":{"type":"string"},"probability":{"type":"integer","minimum":1,"maximum":5},"impact":{"type":"integer","minimum":1,"maximum":5},"risk_score":{"type":"integer","description":"probability × impact. Computed: read-only."},"status":{"type":"string","enum":["identified","monitoring","triggered","mitigated","closed"]}}},"Invoice":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string"},"amount":{"type":"number","description":"Face value. Sent as a JSON number; returned as a STRING so no precision is lost in transit."},"currency":{"type":"string"},"status":{"type":"string","enum":["draft","submitted","approved","paid","disputed","cancelled"]},"due_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true},"paid_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26","nullable":true}}},"CostEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"category":{"type":"string","description":"Your cost code or ledger head. It is the grouping key in every cost report."},"description":{"type":"string","nullable":true},"amount":{"type":"number","description":"As booked, in `currency`. Sent as a JSON number; returned as a STRING so no precision is lost in transit."},"currency":{"type":"string"},"entry_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26"}}},"CostEntryCreate":{"type":"object","required":["category","amount","entry_date"],"properties":{"category":{"type":"string","minLength":1,"maxLength":120,"example":"CIVIL"},"description":{"type":"string","maxLength":1000},"amount":{"type":"number","exclusiveMinimum":0},"currency":{"type":"string","minLength":3,"maxLength":3,"default":"INR"},"amount_inr":{"type":"number","exclusiveMinimum":0,"description":"The INR equivalent, if you already know it."},"exchange_rate_to_inr":{"type":"number","exclusiveMinimum":0,"description":"Rate at the time of the transaction."},"entry_date":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26"}},"description":"A non-INR cost MUST carry either `amount_inr` or `exchange_rate_to_inr`. Without one it cannot be included in any INR total, and a cost silently counted at face value is how a USD 100,000 line reads as ₹1,00,000."},"Settlement":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"schedule_id":{"type":"string","format":"uuid","nullable":true,"description":"null means off-schedule cash."},"direction":{"type":"string","enum":["receipt","payment"]},"amount":{"type":"number","description":"What moved. Sent as a JSON number; returned as a STRING so no precision is lost in transit."},"currency":{"type":"string"},"settled_on":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26"},"method":{"type":"string","nullable":true},"reference":{"type":"string","nullable":true},"counterparty":{"type":"string","nullable":true}}},"SettlementCreate":{"type":"object","required":["direction","amount","settled_on"],"properties":{"schedule_id":{"type":"string","format":"uuid","nullable":true,"description":"The planned line this settles. Omit for money that was never scheduled."},"direction":{"type":"string","enum":["receipt","payment"],"description":"Must match the line's own type when `schedule_id` is given."},"amount":{"type":"number","exclusiveMinimum":0},"currency":{"type":"string","minLength":3,"maxLength":3,"default":"INR"},"settled_on":{"type":"string","format":"date","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-07-26"},"method":{"type":"string","maxLength":40,"example":"RTGS"},"reference":{"type":"string","maxLength":120,"example":"UTR8841200937"},"counterparty":{"type":"string","maxLength":200},"category":{"type":"string","maxLength":80},"notes":{"type":"string","maxLength":1000},"allow_overpayment":{"type":"boolean","default":false,"description":"Settling past a line's outstanding balance is refused with 409 unless this is true. Overpayment is legitimate: escalation, interest, a rounded transfer: but it has to be deliberate, or a mistyped extra zero reads as \"settled in full\"."}}}}}}