Skip to content

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.

Alternatives below are OR choices; scopes within each choice are required together.

[
{
"DelegatedOAuth": [
"ledger.journal-drafts:write"
]
}
]
{
"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
}
}

path · required

Company must belong to token tenant and current installation/principal scope. Inaccessible and absent companies return indistinguishable 404.

{
"type": "string",
"format": "uuid"
}

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"

Required.

JournalDraftRequest

{
"$ref": "#/components/schemas/JournalDraftRequest"
}

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 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"
}
}
]
}

Draft created (or original outcome replayed), never submitted or posted.

Content type: application/json

JournalDraftCreated

{
"$ref": "#/components/schemas/JournalDraftCreated"
}

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"
}

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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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

Problem

{
"$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
}
}