Create an ordinary manual draft as a delegated user
Draft contract · no live API. Generated from OpenAPI 3.1.1, version 1.0.0-draft.
Download the exact YAML. SHA-256: edb36682e0c124e1a67cfc6213ed93dcfa2494e84c63f79181ae7ed12f9851e7.
POST /api/v1/companies/{companyId}/ledger/journal-drafts
Section titled “POST /api/v1/companies/{companyId}/ledger/journal-drafts”Operation ID: createJournalDraft
DRAFT pending owner decision. Delegated USER only; recheck ledger.journal.draft, own-draft limitations, installation/company visibility and module. No machine access, submit, approve or post. Adapter uses the admitted domain draft service; no raw repository write. Idempotency namespace: API major + verified tenant + path company + OAuth client + user + operationId. Reject duplicate JSON keys, unknown fields and unsupported precision; canonical digest sorts object keys, preserves array order/string content and uses UTF-8 JSON without insignificant whitespace. No Unicode normalization or hidden field/default conversion. Same key and canonical payload returns original 201 body, Location and ETag after current authorization; header Idempotency-Replayed is true. A different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent equal requests serialize; outstanding work returns 409 IDEMPOTENCY_IN_PROGRESS with Retry-After. Command, audit, key digest and result reference commit atomically in the same transaction; rollback leaves no successful receipt. Response replay retained >=7 days; retained tombstone after expiry returns 409 IDEMPOTENCY_EXPIRED, never reexecutes. Revalidate current authority before any replay. Authorization revocation and write serialize so one wins; no stale authorization can commit after revocation wins. Resource Location is identity, not a promise of a GET operation in this slice. Mutating draft updates/If-Match are outside this slice.
Authorization
Section titled “Authorization”Alternatives below are OR choices; scopes within each choice are required together.
[ { "DelegatedOAuth": [ "ledger.journal-drafts:write" ] }]Ownership and limits
Section titled “Ownership and limits”{ "x-domain-owner": "Ledger", "x-permissions": [ "ledger.journal.draft" ], "x-modules": [ "ledger" ], "x-principal-kinds": [ "USER" ], "x-data-classification": "Tenant-confidential manual draft; private sources excluded", "x-audit-class": "ledger.journal.draft.created", "x-limits": { "requestsPerMinute": 60, "burst": 10, "concurrency": 10, "maxBodyBytes": 1048576 }}Parameters
Section titled “Parameters”companyId
Section titled “companyId”path · required
Company must belong to token tenant and current installation/principal scope. Inaccessible and absent companies return indistinguishable 404.
{ "type": "string", "format": "uuid"}Idempotency-Key
Section titled “Idempotency-Key”header · required
Random client key, 16–128 safe ASCII [A-Za-z0-9._~-]. Required for POST; missing/invalid is 400 IDEMPOTENCY_KEY_REQUIRED/INVALID_REQUEST. See operation for namespace and replay policy.
{ "type": "string", "minLength": 16, "maxLength": 128, "pattern": "^[A-Za-z0-9._~-]{16,128}$"}Example:
"lk-draft-20261106-0001"Request body
Section titled “Request body”Required.
application/json
Section titled “application/json”{ "$ref": "#/components/schemas/JournalDraftRequest"}LK request
Section titled “LK request”LK synthetic fixture; no real company or tax assertion
{ "postingDate": "2026-11-06", "narration": "Synthetic ordinary manual draft", "lines": [ { "accountId": "11000000-0000-4000-8000-000000000002", "side": "DEBIT", "money": { "amount": "1250.00", "currency": "LKR" } }, { "accountId": "11000000-0000-4000-8000-000000000001", "side": "CREDIT", "money": { "amount": "1250.00", "currency": "LKR" } } ]}SG request
Section titled “SG request”SG synthetic fixture; no real company or tax assertion
{ "postingDate": "2026-11-06", "narration": "Synthetic ordinary manual draft", "lines": [ { "accountId": "21000000-0000-4000-8000-000000000002", "side": "DEBIT", "money": { "amount": "125.00", "currency": "SGD" } }, { "accountId": "21000000-0000-4000-8000-000000000001", "side": "CREDIT", "money": { "amount": "125.00", "currency": "SGD" } } ]}Responses
Section titled “Responses”Draft created (or original outcome replayed), never submitted or posted.
Content type: application/json
{ "$ref": "#/components/schemas/JournalDraftCreated"}201 LK example
Section titled “201 LK example”LK synthetic fixture; no real company or tax assertion
{ "id": "14000000-0000-4000-8000-000000000001", "companyId": "10000000-0000-4000-8000-000000000001", "version": 1, "status": "DRAFT"}201 SG example
Section titled “201 SG example”SG synthetic fixture; no real company or tax assertion
{ "id": "24000000-0000-4000-8000-000000000001", "companyId": "20000000-0000-4000-8000-000000000001", "version": 1, "status": "DRAFT"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }, "Location": { "description": "Canonical identity of the new draft; no read endpoint in P1.", "schema": { "type": "string", "format": "uri-reference" }, "example": "/api/v1/companies/10000000-0000-4000-8000-000000000001/ledger/journal-drafts/14000000-0000-4000-8000-000000000001" }, "ETag": { "description": "Strong quoted decimal version for future If-Match updates.", "schema": { "type": "string", "pattern": "^\"[1-9][0-9]*\"$" }, "example": "\"1\"" }, "Idempotency-Replayed": { "description": "true on successful replay; false on first creation.", "schema": { "type": "boolean" } }}Malformed request, unknown fields, invalid cursor, conflicting tenant/company headers or missing/invalid Idempotency-Key.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:invalid-request", "title": "Invalid request", "status": 400, "code": "INVALID_REQUEST", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Missing/expired/invalid/wrong issuer or audience token; ID tokens are not API credentials.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:auth-required", "title": "Auth required", "status": 401, "code": "AUTH_REQUIRED", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }, "WWW-Authenticate": { "description": "Bearer challenge with no sensitive details.", "schema": { "type": "string" }, "example": "Bearer realm=\"ginum-api\", error=\"invalid_token\"" }}Operation denied by current scope/grant/module policy; machine writes forbidden. No confidential resource existence disclosed.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:scope-denied", "title": "Scope denied", "status": 403, "code": "SCOPE_DENIED", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Resource absent or inaccessible; identical response shape.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:not-found-or-denied", "title": "Not found or denied", "status": 404, "code": "NOT_FOUND_OR_DENIED", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}IDEMPOTENCY_CONFLICT for changed payload, IDEMPOTENCY_IN_PROGRESS (retryable), IDEMPOTENCY_EXPIRED (not retryable), or CURSOR_EXPIRED (restart pagination).
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:idempotency-conflict", "title": "Idempotency conflict", "status": 409, "code": "IDEMPOTENCY_CONFLICT", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }, "Retry-After": { "description": "Present only for IDEMPOTENCY_IN_PROGRESS; integer delay seconds. Other conflicts are not automatically retried.", "schema": { "type": "integer", "minimum": 1, "maximum": 300 } }}Request exceeds 1 MiB.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:body-too-large", "title": "Body too large", "status": 413, "code": "BODY_TOO_LARGE", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Only application/json UTF-8 request bodies are accepted.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:unsupported-media-type", "title": "Unsupported media type", "status": 415, "code": "UNSUPPORTED_MEDIA_TYPE", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Well-formed input violates the narrow draft contract or current domain rules; safe field codes only.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:domain-validation-failed", "title": "Domain validation failed", "status": 422, "code": "DOMAIN_VALIDATION_FAILED", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Quota exhausted; bounded retry only after Retry-After.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:rate-limited", "title": "Rate limited", "status": 429, "code": "RATE_LIMITED", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": true, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }, "Retry-After": { "description": "Nonnegative integer delay seconds, bounded at 300.", "schema": { "type": "integer", "minimum": 1, "maximum": 300 }, "example": 30 }}Unexpected failure; no implementation details. A write outcome may be uncertain; reconcile by retrying the same key only.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:internal-error", "title": "Internal error", "status": 500, "code": "INTERNAL_ERROR", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": false, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }}Dependency, authorization freshness, audit or quota authority unavailable; fail closed.
Content type: application/problem+json
{ "$ref": "#/components/schemas/Problem"}Example:
{ "type": "urn:ginum:problem:temporarily-unavailable", "title": "Temporarily unavailable", "status": 503, "code": "TEMPORARILY_UNAVAILABLE", "requestId": "90000000-0000-4000-8000-000000000001", "retryable": true, "instance": "urn:uuid:90000000-0000-4000-8000-000000000001"}Response headers:
{ "X-Request-Id": { "description": "Server-generated UUID correlation ID. New per HTTP attempt, including idempotent replay.", "schema": { "type": "string", "format": "uuid" } }, "Cache-Control": { "description": "No shared or private response caching.", "schema": { "type": "string", "const": "no-store" } }, "X-RateLimit-Limit": { "description": "Proposed custom quota header: current caller bucket capacity per 60-second window; no other tenant usage.", "schema": { "type": "integer", "minimum": 1 } }, "X-RateLimit-Remaining": { "description": "Remaining requests in current caller bucket.", "schema": { "type": "integer", "minimum": 0 } }, "X-RateLimit-Reset": { "description": "Seconds until this caller bucket resets.", "schema": { "type": "integer", "minimum": 0 } }, "Retry-After": { "description": "Nonnegative integer delay seconds, bounded at 300.", "schema": { "type": "integer", "minimum": 1, "maximum": 300 }, "example": 30 }}
