{
  "openapi": "3.1.0",
  "info": {
    "version": "4.2.0",
    "title": "21yield Public Ingest and Read API",
    "x-documentation-date": "2026-09-14",
    "x-source-commit": "6655f5d",
    "description": "Authenticate with a bearer API key. Ingest, read, and sync requests are\ntenant-scoped to that key.\n\nPOST `/ingest` sends one event. POST `/ingest/batch` sends 1-100 events of\none category. Body size is at most 1 MiB. Categories are `client`,\n`disbursement`, `payment`, and `operation`. Unknown fields are rejected.\nMoney values are rounded to cents.\n\nRetry the same idempotency key and body to receive the original status and\nbody. A different body under a stored key returns 409. Creates and deletes\nreturn 201. Updates return 201 when the stored record is written and 200\nwhen the payload matches stored content. List cursors are opaque tokens.\nContent hashes are version 2.\n"
  },
  "servers": [
    {
      "url": "https://sandbox.api.21yield.com",
      "description": "Sandbox"
    },
    {
      "url": "https://api.21yield.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Event Ingestion",
      "description": "Single and batch business events."
    },
    {
      "name": "Read Data",
      "description": "Records scoped to your API key."
    },
    {
      "name": "Synchronization",
      "description": "Compact version-2 content-hash state."
    },
    {
      "name": "Public",
      "description": "Unauthenticated service health."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "Public"
        ],
        "summary": "Health Check",
        "description": "Health check endpoint (no authentication required)",
        "operationId": "healthCheck",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/HealthResponse"
          }
        }
      }
    },
    "/ingest": {
      "post": {
        "tags": [
          "Event Ingestion"
        ],
        "summary": "Ingest one business event",
        "operationId": "ingestEvent",
        "description": "Synchronously process a client, disbursement, payment, or operation event. Choose create,\nupdate, or delete in data.step. Parent records must exist before child events.\nBody limit: 1 MiB. Only declared envelope/data fields are accepted.\n\nThe same stored key and original JSON content replay the exact original status/body;\nobject property order does not matter. Changed content under a stored key returns\n409 IDEMPOTENCY_KEY_REUSED, even if monetary values round to the same cents.\nKeys are tenant-scoped and remain reserved while their event records exist; there is no cache expiry.\nKnown terminal processing errors are replayable; pre-persistence validation and unexpected\nrolled-back failures are not stored terminal replays. Legacy pending events can return\nIDEMPOTENCY_REQUEST_IN_PROGRESS.\n\nCreates, changed updates and deletes return 201. Hash-matching updates return 200 with\naction=unchanged and no domain writes/timestamp changes/generated effects, though an event is recorded.\nMoney is rounded by the API to cents, halfway away from zero. See /docs/money,\n/docs/batch and /docs/sync for rounding, batch limits, and hash reconciliation.\n",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/IdempotencyKey"
            }
          },
          {
            "name": "X-Correlation-ID",
            "in": "header",
            "description": "Optional tracing ID, returned by the API; generated if omitted.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequest"
              },
              "examples": {
                "client": {
                  "$ref": "#/components/examples/client"
                },
                "company": {
                  "$ref": "#/components/examples/clientCompany"
                },
                "loan": {
                  "$ref": "#/components/examples/disbursementLoan"
                },
                "purchase": {
                  "$ref": "#/components/examples/disbursementPurchase"
                },
                "operation": {
                  "$ref": "#/components/examples/operation"
                },
                "paidPayment": {
                  "$ref": "#/components/examples/payment"
                },
                "expectedPayment": {
                  "$ref": "#/components/examples/paymentExpected"
                },
                "purchasePayment": {
                  "$ref": "#/components/examples/paymentPurchase"
                },
                "partialUpdate": {
                  "$ref": "#/components/examples/paymentUpdate"
                },
                "delete": {
                  "$ref": "#/components/examples/paymentDelete"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/IngestSuccess"
          },
          "201": {
            "$ref": "#/components/responses/IngestSuccess"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/SchemaNotReady"
          }
        }
      }
    },
    "/ingest/batch": {
      "post": {
        "tags": [
          "Event Ingestion"
        ],
        "summary": "Ingest a same-category batch",
        "operationId": "ingestBatch",
        "security": [
          {
            "apiKey": []
          }
        ],
        "description": "Submit 1-100 items of one category with per-item idempotency_key values. No request-level\nIdempotency-Key is required. Items execute sequentially with per-item rollback boundaries.\nThis is not an all-or-nothing business batch: successful items can coexist with failures.\nA completed request returns 200 and ordered results; inspect every result's status/body.\nExact item retries replay original results; changed content under a stored key returns an item 409.\n\nMalformed envelopes/items, empty/oversized item arrays, or mixed categories fail the whole\nrequest with 400. Authentication, body size (1 MiB), readiness, and transaction-level failures\ncan fail the entire request. Send clients before disbursements, then payments/operations.\nSee /docs/batch for complete examples and retries.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchIngestRequest"
              },
              "examples": {
                "clientBatch": {
                  "$ref": "#/components/examples/clientBatch"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch completed; results preserve input order. Each item has its own HTTP status and single-ingest body. Successful items may coexist with 400/404/409/500 items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "minItems": 1,
                      "maxItems": 100,
                      "items": {
                        "type": "object",
                        "required": [
                          "status",
                          "body"
                        ],
                        "properties": {
                          "status": {
                            "type": "integer",
                            "minimum": 100,
                            "maximum": 599
                          },
                          "body": {
                            "anyOf": [
                              {
                                "$ref": "#/components/schemas/IngestResult"
                              },
                              {
                                "$ref": "#/components/schemas/ApiError"
                              },
                              {
                                "type": "object",
                                "description": "Exact historical replay body.",
                                "required": [
                                  "status",
                                  "idempotency_key",
                                  "event_id"
                                ]
                              }
                            ]
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "results": [
                    {
                      "status": 201,
                      "body": {
                        "status": "added",
                        "action": "deleted",
                        "id": "C-1",
                        "idempotency_key": "delete-c1-001",
                        "event_id": 42
                      }
                    },
                    {
                      "status": 400,
                      "body": {
                        "error": "Payload validation failed",
                        "details": [
                          "Unknown field: content_hash"
                        ]
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/SchemaNotReady"
          }
        }
      }
    },
    "/api/tables/{table}": {
      "get": {
        "tags": [
          "Read Data"
        ],
        "summary": "List table records",
        "description": "Return records for one allowed logical table. Results are scoped to the API key in\nthe `Authorization` header.\n\nAllowed tables:\n\n- `clients`\n- `disbursements`\n- `payments`\n- `operations`\n- `purchase_clients`\n- `asset_status`\n- `disbursement_status`\n- `write_offs`\n- `rebates`\n- `extensions`\n- `restructures`\n",
        "operationId": "listTableRecords",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "table",
            "in": "path",
            "required": true,
            "description": "Logical table name to read.",
            "schema": {
              "type": "string",
              "enum": [
                "clients",
                "disbursements",
                "payments",
                "operations",
                "purchase_clients",
                "asset_status",
                "disbursement_status",
                "write_offs",
                "rebates",
                "extensions",
                "restructures"
              ]
            },
            "example": "payments"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Rows per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            },
            "example": 25
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque token from pagination.next_cursor. URL-encode and reuse it with the same filters/sort. Raw IDs/timestamps are invalid. Sort ties use the primary key; rows with null sort values are excluded.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "description": "Column to sort on. Defaults to `created_at`; `asset_status` and `disbursement_status` default to `on_date`.",
            "schema": {
              "type": "string",
              "default": "created_at"
            },
            "example": "payment_date"
          },
          {
            "name": "sort_order",
            "in": "query",
            "required": false,
            "description": "Sort direction.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "include_count",
            "in": "query",
            "required": false,
            "description": "Include `total` and `total_pages` in the pagination object.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "date_field",
            "in": "query",
            "required": false,
            "description": "Column used by `start_date` and `end_date`. Required when either date filter is present.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "description": "Inclusive lower date bound for `date_field`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "description": "Inclusive upper date bound for `date_field`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Partial match across searchable ID and status columns for the table.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Records returned for the requested table.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "required": [
                        "limit",
                        "has_more"
                      ],
                      "properties": {
                        "limit": {
                          "type": "integer",
                          "example": 25
                        },
                        "has_more": {
                          "type": "boolean",
                          "example": false
                        },
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "example": "eyJpZCI6IlBBWV8yMDI0LTAwMSJ9"
                        },
                        "total": {
                          "type": "integer",
                          "example": 125
                        },
                        "total_pages": {
                          "type": "integer",
                          "example": 5
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "payments": {
                    "summary": "Payments list",
                    "value": {
                      "data": [
                        {
                          "payment_id": "PAY-2024-001",
                          "disbursement_id": "DSB-2024-001",
                          "payment_total": 250,
                          "currency": "USD"
                        }
                      ],
                      "pagination": {
                        "limit": 25,
                        "has_more": false
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/tables/{table}/ids": {
      "get": {
        "tags": [
          "Read Data"
        ],
        "summary": "List table IDs",
        "description": "Return business primary-key IDs for one allowed logical table (for example\n`client_id`, `disbursement_id`). Results are scoped to the API key in the\n`Authorization` header.\n\nUse this for weekly or bulk sync reconcile so the client can decide\n`create` vs `update` without probing `POST /ingest`. IDs are ordered\nascending by primary key. Pass `pagination.next_cursor` as `cursor` to\nfetch the next page.\n\nAllowed tables:\n\n- `clients`\n- `disbursements`\n- `payments`\n- `operations`\n- `purchase_clients`\n- `asset_status`\n- `disbursement_status`\n- `write_offs`\n- `rebates`\n- `extensions`\n- `restructures`\n",
        "operationId": "listTableIds",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "table",
            "in": "path",
            "required": true,
            "description": "Logical table name to read.",
            "schema": {
              "type": "string",
              "enum": [
                "clients",
                "disbursements",
                "payments",
                "operations",
                "purchase_clients",
                "asset_status",
                "disbursement_status",
                "write_offs",
                "rebates",
                "extensions",
                "restructures"
              ]
            },
            "example": "clients"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "IDs per page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 50
            },
            "example": 50
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque token from pagination.next_cursor, not the last ID itself.\nURL-encode the token and preserve the table. Invalid cursors return 400.\nResults remain ordered by ascending business ID.\n",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Primary-key IDs returned for the requested table.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Business primary-key values for the table."
                    },
                    "pagination": {
                      "type": "object",
                      "required": [
                        "limit",
                        "has_more"
                      ],
                      "properties": {
                        "limit": {
                          "type": "integer",
                          "example": 50
                        },
                        "has_more": {
                          "type": "boolean",
                          "example": true
                        },
                        "next_cursor": {
                          "type": "string",
                          "description": "Present when `has_more` is true. Pass as `cursor` for the next page.",
                          "example": "eyJ2IjoxLCJzIjoiY2xpZW50LTAwMiIsImsiOltdfQ"
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "clients": {
                    "summary": "Client IDs page",
                    "value": {
                      "data": [
                        "client-001",
                        "client-002"
                      ],
                      "pagination": {
                        "limit": 50,
                        "has_more": true,
                        "next_cursor": "eyJ2IjoxLCJzIjoiY2xpZW50LTAwMiIsImsiOltdfQ"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/tables/{table}/{id}": {
      "get": {
        "tags": [
          "Read Data"
        ],
        "summary": "Get one table record",
        "description": "Return one record from an allowed logical table by primary key. Results are scoped\nto the API key in the `Authorization` header.\n",
        "operationId": "getTableRecord",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "table",
            "in": "path",
            "required": true,
            "description": "Logical table name to read.",
            "schema": {
              "type": "string",
              "enum": [
                "clients",
                "disbursements",
                "payments",
                "operations",
                "purchase_clients",
                "asset_status",
                "disbursement_status",
                "write_offs",
                "rebates",
                "extensions",
                "restructures"
              ]
            },
            "example": "disbursements"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Primary key value for the requested table record.",
            "schema": {
              "type": "string"
            },
            "example": "DSB-2024-001"
          }
        ],
        "responses": {
          "200": {
            "description": "Record returned for the requested table and ID.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                },
                "examples": {
                  "disbursement": {
                    "summary": "Disbursement record",
                    "value": {
                      "data": {
                        "disbursement_id": "DSB-2024-001",
                        "client_id": "BR-2024-001",
                        "disbursement_amount": 10000,
                        "currency": "USD"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/sync-state/{table}": {
      "get": {
        "tags": [
          "Synchronization"
        ],
        "summary": "List IDs and content hashes",
        "operationId": "listSyncState",
        "security": [
          {
            "apiKey": []
          }
        ],
        "description": "Compact tenant-scoped state for reconciliation, ordered by ascending business ID.\nHashes use version 2. Follow opaque next_cursor tokens, not raw IDs.\nOnly compare hashes using the same canonicalization contract; do not hash arbitrary JSON.\nSee /docs/sync and /docs/money. A page containing an uninitialized stored hash returns 409;\ncontact support rather than resetting data. This rounding update alone needs no backfill.\n",
        "parameters": [
          {
            "name": "table",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "clients",
                "disbursements",
                "operations",
                "payments"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5000,
              "default": 1000
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque token returned in pagination.next_cursor. URL-encode it and preserve the same table.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Versioned stored state; next_cursor is present only if has_more is true.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "content_hash_version",
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "content_hash_version": {
                      "type": "integer",
                      "const": 2
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "content_hash"
                        ],
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "content_hash": {
                            "type": "string",
                            "pattern": "^[0-9a-f]{64}$"
                          }
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "required": [
                        "limit",
                        "has_more"
                      ],
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "has_more": {
                          "type": "boolean"
                        },
                        "next_cursor": {
                          "type": "string",
                          "description": "Opaque continuation token."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "content_hash_version": 2,
                  "data": [],
                  "pagination": {
                    "limit": 1000,
                    "has_more": false
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "A row on the returned page lacks a valid stored hash. Pause hash reconciliation and contact support. Do not reset data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "CONTENT_HASH_NOT_INITIALIZED",
                  "details": "Reset and reingest tenant data before using sync-state."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/SchemaNotReady"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "Tenant API key. Public clients do not need an internal service secret."
      }
    },
    "responses": {
      "HealthResponse": {
        "description": "Health check response",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "status": {
                  "type": "string",
                  "example": "ok"
                },
                "environment": {
                  "type": "string",
                  "example": "prod"
                },
                "timestamp": {
                  "type": "string",
                  "format": "date-time",
                  "example": "2024-10-20T12:34:56.789Z"
                }
              }
            }
          }
        }
      },
      "IngestSuccess": {
        "description": "Created, changed, or deleted (201); unchanged (200). Exact retries replay the original status/body. Historical events may replay a response without action, id, or content_hash.",
        "headers": {
          "X-Correlation-ID": {
            "schema": {
              "type": "string"
            },
            "description": "Request tracing ID; not an idempotency key."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/IngestResult"
                },
                {
                  "type": "object",
                  "description": "Historical successful replay, before action/content-hash response fields.",
                  "required": [
                    "status",
                    "idempotency_key",
                    "event_id"
                  ],
                  "properties": {
                    "status": {
                      "const": "added"
                    },
                    "idempotency_key": {
                      "type": "string"
                    },
                    "event_id": {
                      "type": [
                        "integer",
                        "string"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid JSON, envelope, key, field value, money overflow, cursor/limit, or parent/client validation failure. Extra monetary decimal places alone are accepted and rounded.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "examples": {
              "unknownField": {
                "value": {
                  "error": "Payload validation failed",
                  "details": [
                    "Unknown field: content_hash"
                  ]
                }
              },
              "invalidKey": {
                "value": {
                  "error": "Invalid or missing Idempotency-Key header",
                  "details": "Idempotency-Key must be nonblank and at most 255 UTF-8 bytes"
                }
              },
              "purchaseMembership": {
                "value": {
                  "status": "error",
                  "error": "Purchase payment client mismatch",
                  "details": "Client 'C-2' is not linked to purchase disbursement 'DSB-1'",
                  "event_id": 42
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized - Invalid credentials",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "string",
                  "example": "Invalid or missing API key"
                }
              }
            },
            "examples": {
              "invalidApiKey": {
                "summary": "Invalid API key",
                "value": {
                  "error": "Invalid or missing API key"
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Requested record or update target does not exist.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "examples": {
              "missingRecord": {
                "value": {
                  "error": "Record not found"
                }
              },
              "missingUpdate": {
                "value": {
                  "status": "error",
                  "error": "Update target not found",
                  "details": "Cannot update payment 'PAY-1' because the row does not exist",
                  "event_id": 42
                }
              }
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflicting key reuse, duplicate business-ID create, restricted disbursement delete, or legacy request still processing. Inspect error; these are different situations.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "examples": {
              "changedRequest": {
                "value": {
                  "status": "error",
                  "error": "IDEMPOTENCY_KEY_REUSED",
                  "message": "Idempotency-Key was already used with a different request",
                  "event_id": 42
                }
              },
              "duplicateId": {
                "value": {
                  "status": "error",
                  "error": "Duplicate create",
                  "details": "client with client_id 'C-1' already exists. Use step 'update' to update this id.",
                  "event_id": 43
                }
              },
              "dependentRows": {
                "value": {
                  "status": "error",
                  "error": "Disbursement deletion restricted",
                  "details": "Cannot delete disbursement 'DSB-1' while active dependent rows exist: payments",
                  "event_id": 44
                }
              },
              "legacyPending": {
                "value": {
                  "status": "processing",
                  "error": "IDEMPOTENCY_REQUEST_IN_PROGRESS",
                  "event_id": 45
                }
              }
            }
          }
        }
      },
      "TooLarge": {
        "description": "Request body exceeds 1 MiB (1048576 bytes).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Request body too large"
            }
          }
        }
      },
      "Error": {
        "description": "Unexpected processing/storage failure. Retry the same content and key after a transient failure; use the correlation ID when contacting support.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Failed to store event"
            }
          }
        }
      },
      "SchemaNotReady": {
        "description": "Tenant setup is not ready. Pause affected requests and contact support. Retrying does not perform setup.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "SCHEMA_NOT_READY",
              "details": "Tenant schema is not ready"
            }
          }
        }
      },
      "Forbidden": {
        "description": "A supplied internal header failed verification. Direct public clients need only their bearer API key; do not send internal service credentials.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "Forbidden - invalid internal secret"
            }
          }
        }
      }
    },
    "schemas": {
      "IdempotencyKey": {
        "type": "string",
        "minLength": 1,
        "maxLength": 255,
        "pattern": "\\S",
        "x-max-utf8-bytes": 255,
        "description": "Nonblank, case-sensitive, tenant-scoped key; at most 255 UTF-8 bytes, not merely characters. UUID recommended, not required. Use the same key and original content for retries; never reuse a stored key for changed content."
      },
      "ClientFields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 255
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 20
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 255
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^[A-Z]{2}$",
            "x-country": true,
            "description": "Assigned ISO 3166-1 alpha-2 country code, uppercase. Unassigned codes such as ZZ are invalid."
          },
          "is_company": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "registration_number": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "gender": {
            "description": "Individuals use male/female. When is_company is true, any submitted gender is replaced with company before validation."
          },
          "contact_person": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 255
          },
          "info": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        },
        "allOf": [
          {
            "if": {
              "required": [
                "is_company"
              ],
              "properties": {
                "is_company": {
                  "const": true
                }
              }
            },
            "then": {
              "required": [
                "contact_person"
              ],
              "properties": {
                "contact_person": {
                  "type": "string",
                  "pattern": "\\S"
                }
              }
            },
            "else": {
              "properties": {
                "gender": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "male",
                    "female",
                    "company",
                    null
                  ]
                }
              }
            }
          },
          {
            "if": {
              "required": [
                "is_company"
              ],
              "properties": {
                "is_company": {
                  "const": false
                }
              }
            },
            "then": {
              "properties": {
                "gender": {
                  "enum": [
                    "male",
                    "female",
                    null
                  ]
                }
              }
            }
          }
        ]
      },
      "ClientCreate": {
        "title": "Client create",
        "allOf": [
          {
            "$ref": "#/components/schemas/ClientFields"
          },
          {
            "required": [
              "client_id",
              "step",
              "name",
              "email",
              "phone",
              "address",
              "country",
              "is_company",
              "registration_number"
            ],
            "properties": {
              "step": {
                "const": "create"
              },
              "name": {
                "not": {
                  "type": "null"
                }
              },
              "email": {
                "not": {
                  "type": "null"
                }
              },
              "phone": {
                "not": {
                  "type": "null"
                }
              },
              "address": {
                "not": {
                  "type": "null"
                }
              },
              "country": {
                "not": {
                  "type": "null"
                }
              },
              "is_company": {
                "not": {
                  "type": "null"
                }
              },
              "registration_number": {
                "not": {
                  "type": "null"
                }
              }
            },
            "if": {
              "required": [
                "is_company"
              ],
              "properties": {
                "is_company": {
                  "const": false
                }
              }
            },
            "then": {
              "required": [
                "gender"
              ],
              "properties": {
                "gender": {
                  "type": "string",
                  "enum": [
                    "male",
                    "female"
                  ]
                }
              }
            }
          }
        ]
      },
      "ClientUpdate": {
        "title": "Client update",
        "allOf": [
          {
            "$ref": "#/components/schemas/ClientFields"
          },
          {
            "required": [
              "client_id",
              "step"
            ],
            "properties": {
              "step": {
                "const": "update"
              }
            }
          }
        ]
      },
      "ClientDelete": {
        "title": "Client delete",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "client_id",
          "step"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "const": "delete"
          }
        }
      },
      "Money": {
        "type": [
          "number",
          "null"
        ],
        "x-money": true,
        "description": "Finite JSON number, not a numeric string. Extra decimals are rounded to the nearest cent, halfway away from zero, before storage, derived calculations and content hashing. Rounded values and derived amounts must fit NUMERIC(15,2), from -9999999999999.99 to 9999999999999.99. No client pre-rounding is required. Ratios and counts are not money. See /docs/money. Null is accepted only for optional money fields."
      },
      "ApiDate": {
        "type": [
          "string",
          "null"
        ],
        "x-api-date": true,
        "pattern": "^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{3}))?(Z|[+-]\\d{2}:\\d{2})?)?$",
        "description": "ISO 8601 date-only or timestamp with a valid calendar date/time. UTC or an explicit offset is recommended. Null is accepted only for optional fields."
      },
      "DisbursementFields": {
        "type": "object",
        "additionalProperties": false,
        "description": "For a partial update, omit kind unless also providing that kind's required client/date fields. The API validates the merged row and parent links. Changes to maturity, or reductions in margin/amount, require change_date and change_reason. Hash-matching updates have no domain side effects.",
        "properties": {
          "disbursement_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "type": "string"
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "loan",
              "purchase",
              null
            ]
          },
          "client_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "purchase_clients": {
            "type": [
              "array",
              "null"
            ],
            "uniqueItems": true,
            "description": "Purchase links unique by trimmed client_id; referenced clients must exist.",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "client_id"
              ],
              "properties": {
                "client_id": {
                  "type": "string",
                  "minLength": 1,
                  "pattern": "\\S"
                }
              }
            }
          },
          "disbursement_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "gross_value": {
            "$ref": "#/components/schemas/Money"
          },
          "upfront_fee": {
            "$ref": "#/components/schemas/Money"
          },
          "total_expected_margin": {
            "$ref": "#/components/schemas/Money"
          },
          "approved_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "disbursement_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "maturity_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "installment_count": {
            "type": [
              "number",
              "null"
            ]
          },
          "operation_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "payment_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "estimated_delivery_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "goods_description": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "goods_status": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "decision_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "decision": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "approved",
              "rejected",
              null
            ]
          },
          "credit_score": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "is_pledged": {
            "deprecated": true,
            "description": "Ignored on input and normalized to true."
          },
          "pledge_creditor": {
            "description": "Defaults to CMK Capital on create or when blank/non-string. Omitted on update preserves the stored creditor."
          },
          "bank_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "insurer_coverage": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "Ratio defaulting to 0.9; not rounded as money."
          },
          "insurer_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "info": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          },
          "change_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "change_reason": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          }
        },
        "allOf": [
          {
            "if": {
              "required": [
                "kind"
              ],
              "properties": {
                "kind": {
                  "const": "loan"
                }
              }
            },
            "then": {
              "required": [
                "client_id",
                "disbursement_date",
                "maturity_date"
              ],
              "properties": {
                "client_id": {
                  "type": "string",
                  "pattern": "\\S"
                },
                "disbursement_date": {
                  "not": {
                    "type": "null"
                  }
                },
                "maturity_date": {
                  "not": {
                    "type": "null"
                  }
                },
                "purchase_clients": {
                  "type": "null"
                }
              }
            }
          },
          {
            "if": {
              "required": [
                "kind"
              ],
              "properties": {
                "kind": {
                  "const": "purchase"
                }
              }
            },
            "then": {
              "required": [
                "operation_date",
                "payment_date",
                "estimated_delivery_date",
                "goods_description",
                "goods_status"
              ],
              "properties": {
                "client_id": {
                  "type": "null"
                },
                "operation_date": {
                  "not": {
                    "type": "null"
                  }
                },
                "payment_date": {
                  "not": {
                    "type": "null"
                  }
                },
                "estimated_delivery_date": {
                  "not": {
                    "type": "null"
                  }
                },
                "goods_description": {
                  "type": "string",
                  "pattern": "\\S"
                },
                "goods_status": {
                  "type": "string",
                  "pattern": "\\S"
                }
              }
            }
          }
        ]
      },
      "DisbursementCreate": {
        "title": "Disbursement create",
        "allOf": [
          {
            "$ref": "#/components/schemas/DisbursementFields"
          },
          {
            "required": [
              "disbursement_id",
              "step",
              "kind",
              "disbursement_amount",
              "total_expected_margin",
              "currency"
            ],
            "properties": {
              "step": {
                "const": "create"
              },
              "kind": {
                "not": {
                  "type": "null"
                }
              },
              "disbursement_amount": {
                "not": {
                  "type": "null"
                }
              },
              "total_expected_margin": {
                "not": {
                  "type": "null"
                }
              },
              "currency": {
                "not": {
                  "type": "null"
                }
              }
            },
            "not": {
              "anyOf": [
                {
                  "required": [
                    "change_date"
                  ]
                },
                {
                  "required": [
                    "change_reason"
                  ]
                }
              ]
            }
          }
        ]
      },
      "DisbursementUpdate": {
        "title": "Disbursement update",
        "allOf": [
          {
            "$ref": "#/components/schemas/DisbursementFields"
          },
          {
            "required": [
              "disbursement_id",
              "step"
            ],
            "properties": {
              "step": {
                "const": "update"
              }
            }
          }
        ]
      },
      "DisbursementDelete": {
        "title": "Disbursement delete",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "disbursement_id",
          "step"
        ],
        "properties": {
          "disbursement_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "const": "delete"
          }
        }
      },
      "PaymentFields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "payment_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "type": "string"
          },
          "disbursement_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "client_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "description": "Required for purchase payments and must belong to purchase_clients. Resolved from the parent for loans."
          },
          "entity_type": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Tolerated but ignored; use disbursement_id."
          },
          "entity_id": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Tolerated but ignored; use disbursement_id."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "client",
              "insurer",
              "other",
              null
            ],
            "description": "Defaults to client."
          },
          "payment_total": {
            "$ref": "#/components/schemas/Money"
          },
          "payment_principal": {
            "$ref": "#/components/schemas/Money"
          },
          "payment_fee": {
            "$ref": "#/components/schemas/Money"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "expected",
              "paid",
              "canceled",
              null
            ]
          },
          "due_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "payment_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "installment_number": {
            "type": [
              "number",
              "null"
            ]
          },
          "bank_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "info": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        }
      },
      "PaymentCreate": {
        "title": "Payment create",
        "allOf": [
          {
            "$ref": "#/components/schemas/PaymentFields"
          },
          {
            "required": [
              "payment_id",
              "step",
              "disbursement_id",
              "payment_total",
              "currency",
              "status",
              "due_date"
            ],
            "properties": {
              "step": {
                "const": "create"
              },
              "disbursement_id": {
                "not": {
                  "type": "null"
                }
              },
              "payment_total": {
                "not": {
                  "type": "null"
                }
              },
              "currency": {
                "not": {
                  "type": "null"
                }
              },
              "status": {
                "not": {
                  "type": "null"
                }
              },
              "due_date": {
                "not": {
                  "type": "null"
                }
              }
            }
          }
        ]
      },
      "PaymentUpdate": {
        "title": "Payment update",
        "allOf": [
          {
            "$ref": "#/components/schemas/PaymentFields"
          },
          {
            "required": [
              "payment_id",
              "step"
            ],
            "properties": {
              "step": {
                "const": "update"
              }
            }
          }
        ]
      },
      "PaymentDelete": {
        "title": "Payment delete",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "payment_id",
          "step"
        ],
        "properties": {
          "payment_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "const": "delete"
          }
        }
      },
      "OperationFields": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "operation_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "type": "string"
          },
          "disbursement_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "entity_type": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Tolerated but ignored; use disbursement_id."
          },
          "entity_id": {
            "type": [
              "string",
              "null"
            ],
            "deprecated": true,
            "description": "Tolerated but ignored; use disbursement_id."
          },
          "operation_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "operation_description": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "operation_start_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "operation_end_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "operation_revenue": {
            "$ref": "#/components/schemas/Money"
          },
          "operation_cost": {
            "$ref": "#/components/schemas/Money"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "location": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "asset_type": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "asset_value": {
            "$ref": "#/components/schemas/Money"
          },
          "asset_description": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "asset_purchase_date": {
            "$ref": "#/components/schemas/ApiDate"
          },
          "asset_condition": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1
          },
          "depreciation_amount": {
            "$ref": "#/components/schemas/Money"
          },
          "depreciation_period": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "daily",
              "monthly",
              "quarterly",
              "yearly",
              null
            ]
          },
          "depreciation_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "straight_line",
              "amortization",
              null
            ]
          },
          "info": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true
          }
        },
        "allOf": [
          {
            "if": {
              "anyOf": [
                {
                  "required": [
                    "depreciation_amount"
                  ]
                },
                {
                  "required": [
                    "depreciation_period"
                  ]
                },
                {
                  "required": [
                    "depreciation_method"
                  ]
                }
              ]
            },
            "then": {
              "anyOf": [
                {
                  "required": [
                    "asset_id"
                  ]
                },
                {
                  "required": [
                    "asset_value"
                  ]
                }
              ]
            }
          }
        ]
      },
      "OperationCreate": {
        "title": "Operation create",
        "allOf": [
          {
            "$ref": "#/components/schemas/OperationFields"
          },
          {
            "required": [
              "operation_id",
              "step",
              "disbursement_id",
              "operation_name"
            ],
            "properties": {
              "step": {
                "const": "create"
              },
              "disbursement_id": {
                "not": {
                  "type": "null"
                }
              },
              "operation_name": {
                "not": {
                  "type": "null"
                }
              }
            }
          }
        ]
      },
      "OperationUpdate": {
        "title": "Operation update",
        "allOf": [
          {
            "$ref": "#/components/schemas/OperationFields"
          },
          {
            "required": [
              "operation_id",
              "step"
            ],
            "properties": {
              "step": {
                "const": "update"
              }
            }
          }
        ]
      },
      "OperationDelete": {
        "title": "Operation delete",
        "type": "object",
        "additionalProperties": false,
        "required": [
          "operation_id",
          "step"
        ],
        "properties": {
          "operation_id": {
            "type": "string",
            "minLength": 1
          },
          "step": {
            "const": "delete"
          }
        }
      },
      "EventFields": {
        "title": "Business event fields",
        "type": "object",
        "additionalProperties": true,
        "required": [
          "category",
          "data"
        ],
        "properties": {
          "category": {
            "type": "string",
            "enum": [
              "client",
              "disbursement",
              "payment",
              "operation"
            ]
          },
          "data": {
            "type": "object",
            "description": "Category/step-specific input; unknown fields and caller-supplied content_hash are rejected."
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "category": {
                  "const": "client"
                }
              }
            },
            "then": {
              "properties": {
                "data": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ClientCreate"
                    },
                    {
                      "$ref": "#/components/schemas/ClientUpdate"
                    },
                    {
                      "$ref": "#/components/schemas/ClientDelete"
                    }
                  ]
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "category": {
                  "const": "disbursement"
                }
              }
            },
            "then": {
              "properties": {
                "data": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/DisbursementCreate"
                    },
                    {
                      "$ref": "#/components/schemas/DisbursementUpdate"
                    },
                    {
                      "$ref": "#/components/schemas/DisbursementDelete"
                    }
                  ]
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "category": {
                  "const": "payment"
                }
              }
            },
            "then": {
              "properties": {
                "data": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PaymentCreate"
                    },
                    {
                      "$ref": "#/components/schemas/PaymentUpdate"
                    },
                    {
                      "$ref": "#/components/schemas/PaymentDelete"
                    }
                  ]
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "category": {
                  "const": "operation"
                }
              }
            },
            "then": {
              "properties": {
                "data": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OperationCreate"
                    },
                    {
                      "$ref": "#/components/schemas/OperationUpdate"
                    },
                    {
                      "$ref": "#/components/schemas/OperationDelete"
                    }
                  ]
                }
              }
            }
          }
        ]
      },
      "IngestRequest": {
        "title": "Single business event",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "category": {},
          "data": {}
        },
        "allOf": [
          {
            "$ref": "#/components/schemas/EventFields"
          }
        ]
      },
      "IngestResult": {
        "type": "object",
        "required": [
          "status",
          "action",
          "id",
          "idempotency_key",
          "event_id"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "added",
              "unchanged"
            ]
          },
          "action": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "unchanged",
              "deleted"
            ]
          },
          "id": {
            "type": "string",
            "description": "Business ID, not the event ID."
          },
          "content_hash": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "Server-computed SHA-256 content hash. Not returned for deletes."
          },
          "idempotency_key": {
            "type": "string"
          },
          "event_id": {
            "type": [
              "integer",
              "string"
            ],
            "description": "Database event identifier; preserve its returned representation."
          }
        },
        "allOf": [
          {
            "if": {
              "properties": {
                "action": {
                  "const": "unchanged"
                }
              }
            },
            "then": {
              "properties": {
                "status": {
                  "const": "unchanged"
                }
              },
              "required": [
                "content_hash"
              ]
            },
            "else": {
              "properties": {
                "status": {
                  "const": "added"
                }
              }
            }
          },
          {
            "if": {
              "properties": {
                "action": {
                  "enum": [
                    "created",
                    "updated"
                  ]
                }
              }
            },
            "then": {
              "required": [
                "content_hash"
              ]
            }
          }
        ]
      },
      "ApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "description": "Error shapes vary by endpoint. Logged domain codes are not necessarily the public error string.",
        "properties": {
          "error": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "details": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "event_id": {
            "type": [
              "integer",
              "string"
            ]
          }
        }
      },
      "BatchIngestRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "items"
        ],
        "description": "All items must share one category. Each has its own key; no request-level Idempotency-Key is required. Total body size is limited to 1 MiB.",
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "idempotency_key"
              ],
              "properties": {
                "idempotency_key": {
                  "$ref": "#/components/schemas/IdempotencyKey"
                },
                "category": {},
                "data": {}
              },
              "allOf": [
                {
                  "$ref": "#/components/schemas/EventFields"
                }
              ]
            }
          }
        }
      }
    },
    "examples": {
      "client": {
        "summary": "Client Event - Register Client",
        "description": "**Category**: `client`\n\nCreate or update client information. Supports both individual (person) and company client types with contact details and identification.\n\n**Use Case**: Client registration or profile update\n\n**Note**: Set `is_company` to `true` for company clients, `false` for individuals. When `is_company` is `true`, `contact_person` is required and `gender` may be omitted. The worker stores company clients with `gender = \"company\"`.\n",
        "value": {
          "category": "client",
          "data": {
            "client_id": "BR-2024-001",
            "step": "create",
            "name": "Acme Corporation Inc.",
            "email": "john.doe@example.com",
            "phone": "+1234567890",
            "address": "123 Main St, New York, NY 10001",
            "country": "US",
            "is_company": true,
            "registration_number": "CR-2024-001234",
            "contact_person": "Jane Smith",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "clientCompany": {
        "summary": "Company client; gender is defaulted by the API",
        "value": {
          "category": "client",
          "data": {
            "step": "create",
            "client_id": "COMPANY-001",
            "name": "Acme Trading",
            "email": "finance@acme.example",
            "phone": "+15550001031",
            "address": "100 Market Street",
            "country": "US",
            "is_company": true,
            "registration_number": "REG-001",
            "contact_person": "Jane Smith"
          }
        }
      },
      "disbursementLoan": {
        "summary": "Disbursement Event - Create Loan Disbursement",
        "description": "**Category**: `disbursement`\n\nCreate a new disbursement with `kind: \"loan\"`. Replaces the former `loan` category.\n\n**Use Case**: New loan disbursement\n\n**Required Fields**: disbursement_id, kind, client_id, step, disbursement_amount, disbursement_date, maturity_date, currency, total_expected_margin\n\n**Optional Fields**: upfront_fee, gross_value, pledge_creditor, bank_ref, info. Use `pledge_creditor` when the disbursement is assigned to another creditor.\n\n**Kind-specific required fields (loan)**: disbursement_date, maturity_date\n\n**Date Fields**: all `_date` fields accept ISO 8601 date input, either date-only or timestamp.\n",
        "value": {
          "category": "disbursement",
          "data": {
            "disbursement_id": "DSB-2024-001",
            "kind": "loan",
            "client_id": "BR-2024-001",
            "step": "create",
            "disbursement_amount": 10000,
            "disbursement_date": "2024-10-20T12:34:56Z",
            "maturity_date": "2025-10-20T12:34:56Z",
            "currency": "USD",
            "total_expected_margin": 1293,
            "upfront_fee": 200,
            "gross_value": 10200,
            "bank_ref": "BANK-REF-12345",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "disbursementPurchase": {
        "summary": "Disbursement Event - Create Purchase Disbursement",
        "description": "**Category**: `disbursement`\n\nCreate a new disbursement with `kind: \"purchase\"`. Replaces the former `purchase` category.\n\n**Use Case**: Purchase / goods financing\n\n**Required Fields**: disbursement_id, kind, step, disbursement_amount, currency, total_expected_margin, operation_date, payment_date, estimated_delivery_date, goods_description, goods_status\n\n**Client linkage**: Optional. Provide `purchase_clients` only when you need explicit purchase-client links. Each item contains only `client_id`; the purchase id comes from the parent `disbursement_id`. Do not send `client_id` for purchase disbursements.\n\n**Optional Fields**: purchase_clients, upfront_fee, gross_value, pledge_creditor, bank_ref, info. Use `pledge_creditor` when the disbursement is assigned to another creditor.\n\n**Kind-specific required fields (purchase)**: operation_date, payment_date, estimated_delivery_date, goods_description, goods_status\n\n**Date Fields**: all `_date` fields accept ISO 8601 date input, either date-only or timestamp.\n",
        "value": {
          "category": "disbursement",
          "data": {
            "disbursement_id": "DSB-2024-002",
            "kind": "purchase",
            "purchase_clients": [
              {
                "client_id": "BR-2024-001"
              }
            ],
            "step": "create",
            "disbursement_amount": 15000,
            "operation_date": "2024-10-20T12:34:56Z",
            "payment_date": "2024-10-20T12:34:56Z",
            "currency": "USD",
            "total_expected_margin": 2000,
            "estimated_delivery_date": "2025-10-20T12:34:56Z",
            "goods_description": "Agricultural Equipment - Tractor",
            "goods_status": "delivered",
            "upfront_fee": 300,
            "gross_value": 15300,
            "bank_ref": "BANK-REF-12345",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "operation": {
        "summary": "Operation Event - Operation And Asset Status",
        "description": "**Category**: `operation`\n\nRecord operation details and optional asset information against a disbursement.\n\n**Use Case**: Track revenue/cost operation data and related asset state.\n\n**Required Fields**: operation_id, step, disbursement_id, operation_name\n\n**Optional Asset Fields**: asset_id, asset_type, asset_value, asset_description, asset_purchase_date, asset_condition\n",
        "value": {
          "category": "operation",
          "data": {
            "operation_id": "OP-2024-001",
            "step": "create",
            "disbursement_id": "DSB-2024-001",
            "operation_name": "Asset-backed revenue operation",
            "operation_description": "Vehicle lease activity",
            "operation_start_date": "2024-10-20T12:34:56Z",
            "operation_revenue": 1000,
            "operation_cost": 250,
            "currency": "USD",
            "asset_id": "AST-2024-001",
            "asset_type": "vehicle",
            "asset_value": 15000,
            "asset_condition": "active",
            "depreciation_amount": 100,
            "depreciation_period": "monthly",
            "depreciation_method": "straight_line",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "payment": {
        "summary": "Payment Event - Record Paid Payment",
        "description": "**Category**: `payment`\n\nRecord a payment transaction against a disbursement. Includes payment ID, amount, date, breakdown of principal, optional fee, installment number, and required `status`.\n\n**Use Case**: Record a received payment for a disbursement\n\n**Required Fields**: payment_id, disbursement_id, step, due_date, payment_total, currency, status\n\n**Optional Fields**: client_id, source, payment_date, payment_principal, payment_fee, installment_number, bank_ref, info\n\n**Purchase payments**: include `client_id` when the parent disbursement has `kind: \"purchase\"`.\n\n**Date Fields**: `due_date` and `payment_date` accept ISO 8601 date input, either date-only or timestamp.\n\n**Formula**: `payment_total = payment_principal + payment_fee` (where `payment_fee` is the interest portion)\n\n**Status Options**: `expected`, `paid`, `canceled` (required)\n\n**Source Options**: `client`, `insurer`, `other` (optional; defaults to `client`)\n\n**Reference**: `disbursement_id` points to the parent disbursement. Legacy `entity_type` and `entity_id` are optional compatibility fields and ignored by new processing.\n",
        "value": {
          "category": "payment",
          "data": {
            "payment_id": "PAY-2024-001",
            "disbursement_id": "DSB-2024-001",
            "step": "create",
            "status": "paid",
            "source": "client",
            "due_date": "2024-10-20",
            "payment_date": "2024-10-20T12:34:56Z",
            "payment_total": 500,
            "payment_principal": 400,
            "payment_fee": 100,
            "installment_number": 1,
            "currency": "USD",
            "bank_ref": "BANK-REF-12345",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "paymentExpected": {
        "summary": "Payment Event - Scheduled Installment (status=expected)",
        "description": "**Category**: `payment`\n\nBuild a repayment schedule by sending one `payment` event per installment with `status: \"expected\"`.\nWhen the payment is received, send an update with the same `payment_id` and `status: \"paid\"`.\nTo cancel an installment, send an update with `status: \"canceled\"`.\n\nThis replaces the removed `schedule` category.\n\n**Use Case**: Creating a planned repayment schedule as a series of expected payments\n\n**Required Fields**: payment_id, disbursement_id, step, due_date, payment_total, currency, status\n\n**Recommended Fields for scheduling**: installment_number, payment_principal, payment_fee, source\n\n**Formula**: `payment_total = payment_principal + payment_fee` (where `payment_fee` is the interest portion)\n\n**Source Options**: `client`, `insurer`, `other` (optional; defaults to `client`)\n\n**Purchase payments**: include `client_id` when the parent disbursement has `kind: \"purchase\"`.\n\n**Date Fields**: `due_date` accepts ISO 8601 date input, either date-only or timestamp.\n",
        "value": {
          "category": "payment",
          "data": {
            "payment_id": "PAY-2024-002",
            "disbursement_id": "DSB-2024-001",
            "step": "create",
            "status": "expected",
            "source": "client",
            "due_date": "2024-11-20",
            "payment_total": 500,
            "payment_principal": 400,
            "payment_fee": 100,
            "installment_number": 2,
            "currency": "USD",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "paymentPurchase": {
        "summary": "Payment Event - Purchase Payment",
        "description": "**Category**: `payment`\n\nRecord a payment against a purchase disbursement. Purchase payments include `client_id`\nso the API can resolve the purchase client allocation.\n\n**Required Fields**: payment_id, disbursement_id, client_id, step, due_date, payment_total, currency, status\n\n**Date Fields**: `due_date` and `payment_date` accept ISO 8601 date input, either date-only or timestamp.\n",
        "value": {
          "category": "payment",
          "data": {
            "payment_id": "PAY-2024-003",
            "disbursement_id": "DSB-2024-002",
            "client_id": "BR-2024-001",
            "step": "create",
            "status": "paid",
            "due_date": "2024-11-20",
            "payment_date": "2024-11-20T12:34:56Z",
            "payment_total": 500,
            "currency": "USD",
            "info": {
              "key": "value"
            }
          }
        }
      },
      "paymentUpdate": {
        "summary": "Partial payment update with API-owned rounding",
        "value": {
          "category": "payment",
          "data": {
            "step": "update",
            "payment_id": "PAY-2024-001",
            "payment_total": 10.255
          }
        }
      },
      "paymentDelete": {
        "summary": "Delete a payment by its business ID",
        "value": {
          "category": "payment",
          "data": {
            "step": "delete",
            "payment_id": "PAY-2024-001"
          }
        }
      },
      "clientBatch": {
        "summary": "Two company creates; same category and independent keys",
        "value": {
          "items": [
            {
              "idempotency_key": "create-company-001",
              "category": "client",
              "data": {
                "step": "create",
                "client_id": "COMPANY-001",
                "name": "Acme Trading",
                "email": "finance@acme.example",
                "phone": "+15550001031",
                "address": "100 Market Street",
                "country": "US",
                "is_company": true,
                "registration_number": "REG-001",
                "contact_person": "Jane Smith"
              }
            },
            {
              "idempotency_key": "create-company-002",
              "category": "client",
              "data": {
                "step": "create",
                "client_id": "COMPANY-002",
                "name": "Example Supply",
                "email": "finance@supply.example",
                "phone": "+15550001032",
                "address": "200 Market Street",
                "country": "US",
                "is_company": true,
                "registration_number": "REG-002",
                "contact_person": "Alex Smith"
              }
            }
          ]
        }
      }
    }
  },
  "x-constants": {
    "validatorUrl": "https://doc.ingest.api.21yield.com/validator"
  },
  "x-tagGroups": [
    {
      "name": "Ingestion",
      "tags": [
        "Event Ingestion"
      ]
    },
    {
      "name": "Retrieval and synchronization",
      "tags": [
        "Read Data",
        "Synchronization"
      ]
    },
    {
      "name": "Service",
      "tags": [
        "Public"
      ]
    }
  ]
}
