{"openapi":"3.1.0","info":{"title":"Debt Collection - Public API","description":"API for customer developers","version":"1.0.0"},"paths":{"/v1/customer":{"post":{"tags":["Customers"],"summary":"Create or update a customer","description":"Create or update a customer (party) via upsert on identifier_type + identifier_value.\n\nReturns **201** when a new customer is created, **200** when an existing customer is updated.","operationId":"upsert_customer_v1_customer_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomerRequestDTO"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomerResponseDTO"}}}},"201":{"description":"A new customer was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertCustomerResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `INVALID_CLIENT_SOURCE`, `INVALID_CONTACT_CHANNELS`, `INVALID_COUNTRY_CODE`, `INVALID_EMAIL`, `INVALID_IDENTIFIER_TYPE`, `INVALID_PHONE_NUMBER`, `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}},"/v1/customer/batch":{"post":{"tags":["Customers"],"summary":"Create or update many customers","description":"Create or update up to 500 customers in one request.\n\nEach item is matched on `identifier_type` + `identifier_value` — the key\nyour own system owns. A match is amended in place; anything else is created.\n\n**Partial success is normal.** The response is always **200** when the\nrequest itself was understood; each item reports its own `outcome`\n(`created`, `updated`, or `failed`) and a rejected item carries the same\n`error_code` the single-customer route would have returned. Check `failed`\nbefore treating the batch as clean.\n\nItems are applied one at a time, each in its own transaction, in the order\nsent. A rejected item leaves no trace and does not stop the ones after it.\n\nRun this before upserting obligations: an obligation naming a customer that\ndoes not exist yet is rejected.","operationId":"batch_upsert_customers_v1_customer_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpsertCustomersRequestDTO"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchWriteResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `INVALID_CLIENT_SOURCE`, `INVALID_CONTACT_CHANNELS`, `INVALID_COUNTRY_CODE`, `INVALID_EMAIL`, `INVALID_IDENTIFIER_TYPE`, `INVALID_PHONE_NUMBER`, `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}},"/v1/customer/{customer_id}":{"patch":{"tags":["Customers"],"summary":"Partially update a customer","description":"Partially update an existing customer by ID.\n\nOnly the fields provided in the request body are updated.\nAt least one field must be provided.","operationId":"update_customer_v1_customer__customer_id__patch","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"customer_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Customer Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerRequestDTO"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found — `error_code` is `CUSTOMER_NOT_FOUND`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is `DUPLICATE_IDENTIFIER`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `INVALID_CLIENT_SOURCE`, `INVALID_CONTACT_CHANNELS`, `INVALID_COUNTRY_CODE`, `INVALID_EMAIL`, `INVALID_IDENTIFIER_TYPE`, `INVALID_PHONE_NUMBER`, `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/obligations":{"post":{"tags":["Obligations"],"summary":"Create a new obligation","description":"Create a new obligation for a customer you have already synced.\n\n- **customer_identifier**: Object with `type` (nit, cedula, cedula_extranjeria, numero_propiedad) and `value`.\n- **reference_number**: Your own reference. Must be unique across your obligations.\n- **amount**: What is owed. Greater than zero, at most 2 decimals.\n- **due_date**: ISO-8601 date string (YYYY-MM-DD).\n- **currency**: ISO 4217 code — if omitted, your organization's currency is used.\n- **external_reference**: Optional second identifier of your own.\n- **description**: Optional free text. This is what our agents read when\n  they contact the customer.\n- **tags**: Optional list of string tags.\n- **line_items**: Optional breakdown. When present their `total` values\n  must sum to `amount` exactly.","operationId":"create_obligation_v1_obligations_post","security":[{"TenantAPIKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObligationInputDTO"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateObligationResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is `DUPLICATE_REFERENCE_NUMBER`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `CUSTOMER_NOT_FOUND`, `DUE_DATE_BEFORE_ISSUE_DATE`, `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Obligations"],"summary":"List obligations","description":"List obligations for the authenticated tenant.\n\nFilter by customer, `status`, and tags, plus pagination. Name the customer\nthe same way every write here does — `customer_identifier_type` together\nwith `customer_identifier_value` — so you never need to hold our ids.\n`party_id` is still accepted for a caller that has one. An identifier\nmatching no customer answers `CUSTOMER_NOT_FOUND` rather than an empty\npage, which would read the same as \"this customer owes nothing\".\n\nUse `?tags=urgent&tags=vip` to filter obligations that have any of the\ngiven tags.\n\n`limit` accepts 1-500 (default 100) and `offset` 0 or more. The bounds are\nenforced here rather than passed through: an unbounded window reaches\nPostgreSQL verbatim, where a negative one is an error and a huge one is a\ntable scan.","operationId":"list_obligations_v1_obligations_get","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"customer_identifier_type","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Identifier Type"}},{"name":"customer_identifier_value","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Customer Identifier Value"}},{"name":"party_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Party Id"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"tags","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Tags"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObligationListResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `CUSTOMER_NOT_FOUND`, `ORGANIZATION_SETTINGS_NOT_FOUND`, `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/obligations/batch":{"post":{"tags":["Obligations"],"summary":"Create or update many obligations","description":"Create or update up to 500 obligations in one request.\n\nEach item is addressed by its **reference_number** — the key your own system\nowns — so you never need to hold our identifiers to keep an obligation up to\ndate. An item whose reference is new is created; one whose reference already\nexists is amended in place.\n\n**Partial success is normal.** The response is always **200** when the\nrequest itself was understood; each item reports its own `outcome`\n(`created`, `updated`, or `failed`) and a rejected item carries the same\n`error_code` the single-obligation routes would have returned. Check\n`failed` before treating the batch as clean.\n\nItems are applied one at a time, each in its own transaction, in the order\nsent. A rejected item leaves no trace and does not stop the ones after it.\n\nAn obligation cannot change customer: send an item whose\n`customer_identifier` differs from the stored one and it is rejected with\n`OBLIGATION_CUSTOMER_MISMATCH` rather than silently re-pointed.\n\n**Omitting a field is not the same as emptying it.** For `tags` and\n`line_items`, leaving the field out of an item leaves what is stored\nalone, while sending `[]` clears it. A sync that simply does not track\ntags therefore cannot wipe the ones a collector added.\n\n**Customers must already exist.** Upsert your customers first; an obligation\nnaming an unknown one fails with `CUSTOMER_NOT_FOUND`.","operationId":"batch_upsert_obligations_v1_obligations_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpsertObligationsRequestDTO"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchWriteResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `ORGANIZATION_SETTINGS_NOT_FOUND`, `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}},"/v1/obligations/tags":{"get":{"tags":["Obligations"],"summary":"List available obligation tags","description":"Return all distinct tag values used across obligations for the authenticated tenant.\n\nTags are returned sorted alphabetically.","operationId":"list_available_obligation_tags_v1_obligations_tags_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObligationTagsResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}},"/v1/obligations/{obligation_id}":{"get":{"tags":["Obligations"],"summary":"Get obligation","description":"Retrieve a single obligation by its ID.","operationId":"get_obligation_v1_obligations__obligation_id__get","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"obligation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Obligation Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicObligationDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found — `error_code` is `NOT_FOUND`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"patch":{"tags":["Obligations"],"summary":"Update an existing obligation","description":"Partially update an obligation.\n\nOnly provided fields are changed. **reference_number is immutable** — it is\nhow you address the obligation, so changing it would make your next sync\ncreate a second one.\n\n- **amount**: New amount owed (must be greater than zero).\n- **due_date**: New ISO-8601 due date.\n- **status**: Manual status override (e.g. `WRITTEN_OFF`). While the debtor has\n  an open or investigating dispute on the obligation, its status stays\n  `DISPUTED`: any other status is refused with 409 `OBLIGATION_UNDER_DISPUTE`\n  (since 2026-09-27; it was applied before). Resolve or withdraw the dispute,\n  then send the status.\n- **tags**: Replacement list of tags (replaces all existing ones).\n- **line_items**: Replacement breakdown — omit to leave unchanged, send an\n  empty list to clear. When non-empty their `total` values must sum to the\n  obligation's resulting amount exactly.","operationId":"update_obligation_v1_obligations__obligation_id__patch","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"obligation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Obligation Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObligationPatchDTO"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObligationUpdatedDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found — `error_code` is `NOT_FOUND`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is `OBLIGATION_UNDER_DISPUTE`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `DUE_DATE_BEFORE_ISSUE_DATE`, `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Obligations"],"summary":"Delete an obligation","description":"Delete an obligation by its ID.\n\nOnly obligations created via the API or manually can be deleted; deleting\none the platform owns is a 409.\n\nThe delete is **idempotent**: an id that does not exist, or belongs to\nanother tenant, answers 204 as well. This endpoint never returns 404, so a\n204 confirms the obligation is absent — not that this call removed it.\n\nOpen disputes do not block the delete; they are rescoped to the party.","operationId":"delete_obligation_v1_obligations__obligation_id__delete","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"obligation_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Obligation Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is one of `CANNOT_DELETE_SYSTEM_RECORD`, `OBLIGATION_HAS_PAYMENT_ALLOCATIONS`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/payments":{"post":{"tags":["Payments"],"summary":"Create or update a payment","description":"Create or update a payment, matched on your own receipt reference.\n\n**reference** is your own receipt/voucher number. A repeat call carrying it\nfor the same customer amends the payment already posted rather than\ncreating a duplicate — including a call that now names an obligation an\nearlier one did not.\n\n**Naming an obligation is optional.** Send\n**obligation_reference_number** and `amount` is the amount applied to that\nobligation; the payment's total grows only if what it has left unapplied\ncannot cover it. Send **customer_identifier** and a `currency` instead and\nthe payment is recorded against that customer with nothing applied — the\nreceipt on account you settle against an obligation later. Sending both is\nallowed, and the obligation must belong to that customer.\n\nReturns **201** when a new payment is created, **200** when an existing\none is amended. Re-sending a payment that has not changed is accepted and\nwrites nothing. `amount` in the response is always the payment's total,\nand `unallocated_amount` what is left of it unapplied.\n\n**An amend never moves the received date.** A posted payment's\n`received_date` is immutable — the day it was received decides which\npromise it satisfied and freezes the days-past-due its commission band is\nread from — so a request naming a different date is refused with\n`PAYMENT_RECEIVED_DATE_IMMUTABLE` rather than silently ignored. A ledger\nthat genuinely corrected the date has to delete the payment and post it\nagain. `currency` is likewise fixed at first post and is ignored on an\namend; it is only read when the payment is created.\n\nAn amount already reserved against a payment promise cannot move either,\nand answers `PAYMENT_AMOUNT_LOCKED_BY_PROMISE`. Restating a payment as\nworth less than it is already applied to answers\n`PAYMENT_AMOUNT_BELOW_ALLOCATED`.","operationId":"upsert_payment_v1_payments_post","security":[{"TenantAPIKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPaymentRequestDTO"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPaymentResponseDTO"}}}},"201":{"description":"A new payment was created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertPaymentResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is one of `OBLIGATION_CUSTOMER_MISMATCH`, `OBLIGATION_FULLY_ALLOCATED`, `PAYMENT_AMOUNT_BELOW_ALLOCATED`, `PAYMENT_AMOUNT_LOCKED_BY_PROMISE`, `PAYMENT_RECEIVED_DATE_IMMUTABLE`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is one of `CUSTOMER_NOT_FOUND`, `OBLIGATION_NOT_FOUND`, `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"tags":["Payments"],"summary":"List payments","description":"List payments for the authenticated tenant.\n\nSupports optional filtering by party and by the calendar-day window the\npayment was received in, plus pagination. `limit` accepts 1-500 (default\n100) and `offset` 0 or more.","operationId":"list_payments_v1_payments_get","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"party_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Party Id"}},{"name":"received_date_from","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Received Date From"}},{"name":"received_date_to","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Received Date To"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentTransactionListResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/payments/batch":{"post":{"tags":["Payments"],"summary":"Create or update many payments","description":"Create or update up to 500 payments in one request.\n\nEach item is matched on **reference** (your own receipt/voucher number)\nfor the customer it belongs to — the keys your own system owns. A match is\namended in place; anything else is created. As on the single-payment\nroute, **obligation_reference_number** is optional: an item without one\nrecords a receipt on account against the **customer_identifier** it names.\n\n**Partial success is normal.** The response is always **200** when the\nrequest itself was understood; each item reports its own `outcome`\n(`created`, `updated`, or `failed`) and a rejected item carries the same\n`error_code` the single-payment route would have returned. Check `failed`\nbefore treating the batch as clean.\n\nItems are applied one at a time, each in its own transaction, in the order\nsent. A rejected item leaves no trace and does not stop the ones after it.\nTwo items may name the same payment: the second amends what the first\nwrote, so a voucher settling several obligations is one payment with an\napplication for each.\n\n**Customers and obligations must already exist.** Upsert them first; an\nitem naming an unknown obligation fails with `OBLIGATION_NOT_FOUND` and one\nnaming an unknown customer with `CUSTOMER_NOT_FOUND`.","operationId":"batch_upsert_payments_v1_payments_batch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchUpsertPaymentsRequestDTO"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchWriteResponseDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}},"/v1/payments/{payment_id}":{"get":{"tags":["Payments"],"summary":"Get payment","description":"Retrieve a single payment by its ID, with its obligation allocations.","operationId":"get_payment_v1_payments__payment_id__get","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"payment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Payment Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentTransactionDetailDTO"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found — `error_code` is `NOT_FOUND`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"tags":["Payments"],"summary":"Delete a payment","description":"Delete a payment by its ID.\n\nOnly payments created via the API or manually can be deleted; deleting one\nthe platform owns is a 409.","operationId":"delete_payment_v1_payments__payment_id__delete","security":[{"TenantAPIKey":[]}],"parameters":[{"name":"payment_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Payment Id"}}],"responses":{"204":{"description":"Successful Response"},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found — `error_code` is `NOT_FOUND`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict — `error_code` is `CANNOT_DELETE_SYSTEM_RECORD`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/":{"get":{"tags":["Service"],"summary":"Confirm credentials and API version","description":"Public API v1 root endpoint.\n\nReturns:\n    Dictionary with API version message and authenticated tenant info.","operationId":"root_v1__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Root V1  Get"}}}},"401":{"description":"Unauthorized — `error_code` is `UNAUTHORIZED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error — `error_code` is `VALIDATION_FAILED`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error_code":"VALIDATION_FAILED","http_status":422,"message":"Request validation failed","details":{"validation_errors":[{"type":"missing","loc":["body","identifier_value"],"msg":"Field required"}]},"trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}},"500":{"description":"Internal Server Error — `error_code` is `INTERNAL_ERROR`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"security":[{"TenantAPIKey":[]}]}}},"components":{"schemas":{"BatchItemErrorDTO":{"properties":{"error_code":{"$ref":"#/components/schemas/PublicErrorCode","description":"Machine-readable error code"},"http_status":{"type":"integer","title":"Http Status","description":"The status the equivalent single-resource call would return"},"message":{"type":"string","title":"Message","description":"Human-readable error message"},"details":{"additionalProperties":{"$ref":"#/components/schemas/JsonValue"},"type":"object","title":"Details","description":"Machine-consumable structured details"}},"type":"object","required":["error_code","http_status","message"],"title":"BatchItemErrorDTO","description":"Why one item was rejected.\n\nCarries the same ``error_code`` vocabulary the single-resource routes raise,\nso an integrator writes one error handler and points it at both. The\n``http_status`` an equivalent single-resource call would have answered is\nincluded for exactly that reuse."},"BatchItemResultDTO":{"properties":{"index":{"type":"integer","title":"Index","description":"Zero-based position of this item in the submitted list"},"key":{"type":"string","title":"Key","description":"The natural key the item was addressed by — an obligation's reference_number, a customer's identifier value. Echoed so a caller can correlate without relying on list order."},"outcome":{"type":"string","enum":["created","updated","failed"],"title":"Outcome","description":"Whether the item was inserted, amended, or rejected"},"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id","description":"Identifier of the written record; null when the item failed"},"error":{"anyOf":[{"$ref":"#/components/schemas/BatchItemErrorDTO"},{"type":"null"}],"description":"Why the item was rejected; null when it succeeded"}},"type":"object","required":["index","key","outcome"],"title":"BatchItemResultDTO","description":"What became of one submitted item."},"BatchUpsertCustomersRequestDTO":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UpsertCustomerRequestDTO"},"type":"array","maxItems":500,"minItems":1,"title":"Items","description":"Customers to upsert (1-500)."}},"type":"object","required":["items"],"title":"BatchUpsertCustomersRequestDTO","description":"A batch of customers to create-or-update.\n\nEach item carries the same fields as a single-customer upsert and is\naddressed the same way: on ``identifier_type`` + ``identifier_value``."},"BatchUpsertObligationsRequestDTO":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ObligationInputDTO"},"type":"array","maxItems":500,"minItems":1,"title":"Items","description":"Obligations to upsert (1-500)."}},"type":"object","required":["items"],"title":"BatchUpsertObligationsRequestDTO","description":"A batch of obligations to create-or-update, addressed by reference_number.\n\nEach item carries the same fields as a single-obligation create. The\ndifference is how an item that already exists is treated: the create route\nrejects it as a duplicate, while here it is amended in place, which is what\na ledger sync needs."},"BatchUpsertPaymentsRequestDTO":{"properties":{"items":{"items":{"$ref":"#/components/schemas/UpsertPaymentRequestDTO"},"type":"array","maxItems":500,"minItems":1,"title":"Items","description":"Payments to upsert (1-500)."}},"type":"object","required":["items"],"title":"BatchUpsertPaymentsRequestDTO","description":"A batch of payments to create-or-update, addressed by reference + customer.\n\nEach item carries the same fields as a single-payment upsert and is\naddressed the same way: on ``reference``, for the customer the item names\ndirectly or through the obligation it settles."},"BatchWriteResponseDTO":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BatchItemResultDTO"},"type":"array","title":"Results"},"created":{"type":"integer","title":"Created","description":"Items that inserted a new record"},"updated":{"type":"integer","title":"Updated","description":"Items that amended an existing record"},"failed":{"type":"integer","title":"Failed","description":"Items rejected without being written"}},"type":"object","required":["results","created","updated","failed"],"title":"BatchWriteResponseDTO","description":"Per-item outcomes for a batch write, plus their tally.\n\n``results`` holds one entry per submitted item, in submission order. The\ncounts are a convenience for the common case of \"did anything fail?\" and\nalways sum to ``len(results)``."},"CreateObligationResponseDTO":{"properties":{"obligation_id":{"type":"string","title":"Obligation Id"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["obligation_id","created_at"],"title":"CreateObligationResponseDTO","description":"Response DTO returned after successfully creating an obligation."},"CustomerContactDTO":{"properties":{"value":{"type":"string","maxLength":200,"minLength":1,"title":"Value"},"type":{"type":"string","title":"Type","description":"Contact type: email, landline, or mobile"},"channels":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Channels","description":"The channels this contact serves — its full capability, not the subset currently switched on. Include anything a read reported under 'deactivated_channels' for the same value: those channels stay switched off either way, and listing them is what keeps a read-then-write round trip from looking like a capability change. Only meaningful for 'mobile', where it must be a non-empty subset of ['sms', 'whatsapp', 'call']; omit it to serve all three. Two mobiles may not declare the same channel, switched on or off."}},"type":"object","required":["value","type"],"title":"CustomerContactDTO","description":"One contact of a customer, with the channels it is enabled for.\n\nAdditive alternative to the flat ``emails`` / ``landlines`` / ``mobiles``\nlists: use it when a number should serve only some of its channels, or when\na customer has more than one mobile. A contact type may be supplied in one\nform or the other, never both.\n\nEntries repeating a value merge into one contact whose channels are the\nunion; across the surviving mobiles each channel may be claimed once."},"CustomerContactResponseDTO":{"properties":{"value":{"type":"string","title":"Value"},"type":{"type":"string","title":"Type"},"channels":{"items":{"type":"string"},"type":"array","title":"Channels"},"deactivated_channels":{"items":{"type":"string"},"type":"array","title":"Deactivated Channels"}},"type":"object","required":["value","type"],"title":"CustomerContactResponseDTO","description":"One stored contact of a customer, including harvested WhatsApp IDs.\n\n``channels`` is what the contact currently serves; ``deactivated_channels``\nis what it serves but has switched off per party. The capability is their\nunion. ``deactivated_channels`` is **read-only** — the switch lives in the\napp, not on this surface — but it is returned so a client syncing its own\nrecords can see why a number it sent is not initiating.\n\n``value`` is the **canonical** form of the address, which for email means\nlower-case regardless of how it was sent (SC5): the canonical form *is* the\nstored form, and the address is stored exactly once. This is the only\nnon-additive API change in the shared-contact-points feature — an\nintegrator doing diff-based sync sees one-time churn on mixed-case emails.\nPhones are unaffected: ``PhoneNumber`` already validated strict E.164, so\nnormalization is the identity function for them."},"CustomerIdentifierDTO":{"properties":{"type":{"type":"string","title":"Type","description":"Identifier type: nit, cedula, cedula_extranjeria, numero_propiedad"},"value":{"type":"string","minLength":1,"title":"Value","description":"Identifier value"}},"type":"object","required":["type","value"],"title":"CustomerIdentifierDTO","description":"Customer identifier used to look up a party."},"ErrorResponse":{"properties":{"error_code":{"type":"string","title":"Error Code","description":"Machine-readable error code"},"http_status":{"type":"integer","title":"Http Status","description":"HTTP status code"},"message":{"type":"string","title":"Message","description":"Human-readable error message"},"details":{"additionalProperties":true,"type":"object","title":"Details","description":"Machine-consumable structured details"},"trace_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Trace Id","description":"OpenTelemetry trace ID for correlation"}},"type":"object","required":["error_code","http_status","message"],"title":"ErrorResponse","description":"Standardized error response structure used by all API surfaces.","examples":[{"details":{},"error_code":"NOT_FOUND","http_status":404,"message":"Resource not found","trace_id":"4bf92f3577b34da6a3ce929d0e0e4736"}]},"JsonValue":{},"ObligationInputDTO":{"properties":{"customer_identifier":{"$ref":"#/components/schemas/CustomerIdentifierDTO","description":"Whose debt this is (e.g. nit, cedula, cedula_extranjeria, numero_propiedad)"},"reference_number":{"type":"string","maxLength":255,"minLength":1,"title":"Reference Number","description":"Your own reference for this obligation. Unique across your obligations."},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount","description":"What is owed. Greater than zero, at most 2 decimals."},"due_date":{"type":"string","title":"Due Date","description":"ISO-8601 date the obligation falls due (YYYY-MM-DD)"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date","description":"ISO-8601 date the obligation was issued"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"ISO 4217 code -- defaults to your organization's currency"},"external_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Reference","description":"A second identifier of your own (e.g. the invoice number in another system)"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free text describing the debt. This is what our agents read when they contact the customer."},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"Free-text internal notes"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","description":"Optional tags for categorisation"},"line_items":{"items":{"$ref":"#/components/schemas/ObligationLineItemInputDTO"},"type":"array","title":"Line Items","description":"Optional breakdown. When given, the totals must sum to `amount` exactly."}},"additionalProperties":false,"type":"object","required":["customer_identifier","reference_number","amount","due_date"],"title":"ObligationInputDTO","description":"One obligation as the customer's own system states it.\n\nShared by the create route and the batch upsert, so the two cannot drift.\nUnknown fields are rejected rather than ignored: a typo in a sync job that\nsilently drops a field is worse than a rejected request."},"ObligationLineItemDTO":{"properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Id"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"total":{"type":"string","title":"Total"},"account_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Code"}},"type":"object","required":["total"],"title":"ObligationLineItemDTO","description":"A single line item (renglón) of an obligation, returned on detail reads."},"ObligationLineItemInputDTO":{"properties":{"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"account_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Code","description":"Optional GL / chart-of-accounts code for this line item (Colombia PUC, US account number, ...). Descriptive metadata."},"total":{"anyOf":[{"type":"number"},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Total","description":"Line item total. May be negative (e.g. a discount/credit line); the sum of all line items must equal the obligation amount. Truncated to 2 decimals (not rounded)."}},"additionalProperties":false,"type":"object","required":["total"],"title":"ObligationLineItemInputDTO","description":"A single line item (renglón) supplied on create/update requests."},"ObligationListItemDTO":{"properties":{"obligation_id":{"type":"string","title":"Obligation Id"},"reference_number":{"type":"string","title":"Reference Number"},"party_id":{"type":"string","title":"Party Id"},"amount":{"type":"string","title":"Amount"},"currency":{"type":"string","title":"Currency"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"due_date":{"type":"string","title":"Due Date"},"outstanding_balance":{"type":"string","title":"Outstanding Balance"},"status":{"type":"string","title":"Status"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["obligation_id","reference_number","party_id","amount","currency","issue_date","due_date","outstanding_balance","status","tags","created_at","updated_at"],"title":"ObligationListItemDTO","description":"One obligation in a list response.\n\nCarries ``amount`` alongside ``outstanding_balance``: without the original\ncharge a partly paid obligation is illegible, and making a caller fetch\neach one to find out turns their list into an N+1."},"ObligationListResponseDTO":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ObligationListItemDTO"},"type":"array","title":"Items"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"count":{"type":"integer","title":"Count"},"summary":{"$ref":"#/components/schemas/ObligationSummaryDTO"}},"type":"object","required":["items","limit","offset","count","summary"],"title":"ObligationListResponseDTO","description":"A page of obligations plus the tenant-wide totals."},"ObligationPatchDTO":{"properties":{"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amount","description":"New amount owed. At most 2 decimals."},"due_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Date","description":"New ISO-8601 due date"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date","description":"New ISO-8601 issue date"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"New ISO 4217 code"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Manual status override (e.g. WRITTEN_OFF)"},"external_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Reference","description":"New second identifier of your own"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New free-text description"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes","description":"New free-text internal notes"},"tags":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tags","description":"Replacement tags (replaces all existing)"},"line_items":{"anyOf":[{"items":{"$ref":"#/components/schemas/ObligationLineItemInputDTO"},"type":"array"},{"type":"null"}],"title":"Line Items","description":"Replacement breakdown -- omit to leave unchanged, send an empty list to clear. When non-empty the totals must sum to the resulting amount exactly."}},"additionalProperties":false,"type":"object","title":"ObligationPatchDTO","description":"A partial change to an obligation. Only the fields you send are applied.\n\n``reference_number`` is not here: it is how you address the obligation, so\nchanging it would make the next sync create a second one."},"ObligationSummaryByCurrencyItemDTO":{"properties":{"total_amount":{"type":"string","title":"Total Amount"},"paid_amount":{"type":"string","title":"Paid Amount"},"pending_amount":{"type":"string","title":"Pending Amount"},"overdue_amount":{"type":"string","title":"Overdue Amount"},"owed_amount":{"type":"string","title":"Owed Amount"}},"type":"object","required":["total_amount","paid_amount","pending_amount","overdue_amount","owed_amount"],"title":"ObligationSummaryByCurrencyItemDTO","description":"Obligation amounts for a single currency.\n\nShow ``owed_amount`` for \"what this debtor still owes\". The other figures\nare narrower on purpose and none of them is the balance:\n\n- ``total_amount``: original principal of every obligation, settled and\n  written off included. Summed from a different column than the rest, so\n  ``total_amount - paid_amount`` is not a balance.\n- ``pending_amount``: actively pursued debt only. Excludes disputed debt by\n  reporting contract, so it shrinks the moment a dispute is raised.\n- ``overdue_amount``: a subset of ``pending_amount``, not an addend.\n- ``owed_amount``: everything still owed, disputed debt included. Raising a\n  dispute leaves it unchanged, which is what makes it safe to show."},"ObligationSummaryDTO":{"properties":{"by_currency":{"additionalProperties":{"$ref":"#/components/schemas/ObligationSummaryByCurrencyItemDTO"},"type":"object","title":"By Currency"}},"type":"object","required":["by_currency"],"title":"ObligationSummaryDTO","description":"Summary of obligation amounts grouped by currency and status for dashboard display."},"ObligationTagsResponseDTO":{"properties":{"tags":{"items":{"type":"string"},"type":"array","title":"Tags"}},"type":"object","required":["tags"],"title":"ObligationTagsResponseDTO","description":"Response DTO for the available obligation tags endpoint."},"ObligationUpdatedDTO":{"properties":{"obligation_id":{"type":"string","title":"Obligation Id"},"reference_number":{"type":"string","title":"Reference Number"},"amount":{"type":"string","title":"Amount"},"due_date":{"type":"string","title":"Due Date"},"currency":{"type":"string","title":"Currency"},"outstanding_balance":{"type":"string","title":"Outstanding Balance"},"status":{"type":"string","title":"Status"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags"},"line_items":{"items":{"$ref":"#/components/schemas/ObligationLineItemDTO"},"type":"array","title":"Line Items","default":[]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"external_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Reference"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["obligation_id","reference_number","amount","due_date","currency","outstanding_balance","status","tags","updated_at"],"title":"ObligationUpdatedDTO","description":"An obligation as this surface returns it after a change."},"PartyIdentifierDTO":{"properties":{"type":{"type":"string","title":"Type","description":"Document type (e.g. cedula, nit, numero_propiedad)"},"value":{"type":"string","title":"Value","description":"Document number as captured"}},"type":"object","required":["type","value"],"title":"PartyIdentifierDTO","description":"A party's identification document, shown beside their name.\n\nOperators disambiguate debtors by document — homonyms are common — so any\nlist that shows a name carries the primary one alongside it, and a screen\nthat settles units carries the property number after it."},"PaymentAllocationOutputDTO":{"properties":{"id":{"type":"string","title":"Id"},"payment_transaction_id":{"type":"string","title":"Payment Transaction Id"},"obligation_id":{"type":"string","title":"Obligation Id"},"obligation_reference_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Obligation Reference Number"},"line_item_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line Item Id"},"amount":{"type":"string","title":"Amount"},"created_at":{"type":"string","title":"Created At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["id","payment_transaction_id","obligation_id","amount","created_at"],"title":"PaymentAllocationOutputDTO","description":"Response DTO for a single payment allocation."},"PaymentTransactionDetailDTO":{"properties":{"id":{"type":"string","title":"Id"},"tenant_id":{"type":"string","title":"Tenant Id"},"party_id":{"type":"string","title":"Party Id"},"party_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Name"},"party_lastname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Lastname"},"party_identifier":{"anyOf":[{"$ref":"#/components/schemas/PartyIdentifierDTO"},{"type":"null"}]},"party_property_number":{"anyOf":[{"$ref":"#/components/schemas/PartyIdentifierDTO"},{"type":"null"}]},"amount":{"type":"string","title":"Amount"},"currency":{"type":"string","title":"Currency"},"received_date":{"type":"string","title":"Received Date"},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference"},"source":{"type":"string","title":"Source"},"unallocated_amount":{"type":"string","title":"Unallocated Amount"},"allocations":{"items":{"$ref":"#/components/schemas/PaymentAllocationOutputDTO"},"type":"array","title":"Allocations"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"},"created_by":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created By"}},"type":"object","required":["id","tenant_id","party_id","amount","currency","received_date","source","unallocated_amount","allocations","created_at","updated_at"],"title":"PaymentTransactionDetailDTO","description":"Response DTO for a single payment transaction with allocations.\n\nCarries the debtor's name and primary document beside ``party_id``, as the list\ndoes, so the detail page can say whose money this is without a second request."},"PaymentTransactionListItemDTO":{"properties":{"id":{"type":"string","title":"Id"},"party_id":{"type":"string","title":"Party Id"},"party_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Name"},"party_lastname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Party Lastname"},"party_identifier":{"anyOf":[{"$ref":"#/components/schemas/PartyIdentifierDTO"},{"type":"null"}]},"party_property_number":{"anyOf":[{"$ref":"#/components/schemas/PartyIdentifierDTO"},{"type":"null"}]},"amount":{"type":"string","title":"Amount"},"currency":{"type":"string","title":"Currency"},"received_date":{"type":"string","title":"Received Date"},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference"},"source":{"type":"string","title":"Source"},"unallocated_amount":{"type":"string","title":"Unallocated Amount"},"created_at":{"type":"string","title":"Created At"}},"type":"object","required":["id","party_id","amount","currency","received_date","source","unallocated_amount","created_at"],"title":"PaymentTransactionListItemDTO","description":"Response DTO for a payment transaction in a list view."},"PaymentTransactionListResponseDTO":{"properties":{"items":{"items":{"$ref":"#/components/schemas/PaymentTransactionListItemDTO"},"type":"array","title":"Items"},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["items","limit","offset","count"],"title":"PaymentTransactionListResponseDTO","description":"Response DTO for listing payment transactions."},"PublicErrorCode":{"type":"string","enum":["INVALID_TOKEN","TOKEN_EXPIRED","MISSING_SCOPE","VALIDATION_FAILED","INVALID_INPUT","INTERNAL_ERROR","NOT_FOUND","UNAUTHORIZED","FORBIDDEN","CUSTOMER_NOT_FOUND","DUPLICATE_REFERENCE_NUMBER","CANNOT_DELETE_SYSTEM_RECORD","DUE_DATE_BEFORE_ISSUE_DATE","OBLIGATION_HAS_PAYMENT_ALLOCATIONS","OBLIGATION_UNDER_DISPUTE","OBLIGATION_CUSTOMER_MISMATCH","LEDGER_HELD_EXTERNALLY","ORGANIZATION_SETTINGS_NOT_FOUND","INVALID_IDENTIFIER_TYPE","INVALID_EMAIL","INVALID_PHONE_NUMBER","INVALID_COUNTRY_CODE","INVALID_CLIENT_SOURCE","INVALID_CONTACT_CHANNELS","DUPLICATE_IDENTIFIER","OBLIGATION_NOT_FOUND","OBLIGATION_FULLY_ALLOCATED","PAYMENT_RECEIVED_DATE_IMMUTABLE","PAYMENT_AMOUNT_LOCKED_BY_PROMISE","PAYMENT_AMOUNT_BELOW_ALLOCATED"],"title":"PublicErrorCode","description":"Error codes for the Public API surface (customer developers)."},"PublicObligationDTO":{"properties":{"obligation_id":{"type":"string","title":"Obligation Id"},"reference_number":{"type":"string","title":"Reference Number"},"party_id":{"type":"string","title":"Party Id"},"amount":{"type":"string","title":"Amount"},"currency":{"type":"string","title":"Currency"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"due_date":{"type":"string","title":"Due Date"},"outstanding_balance":{"type":"string","title":"Outstanding Balance"},"status":{"type":"string","title":"Status"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"external_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Reference"},"tags":{"items":{"type":"string"},"type":"array","title":"Tags","default":[]},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["obligation_id","reference_number","party_id","amount","currency","issue_date","due_date","outstanding_balance","status","created_at","updated_at"],"title":"PublicObligationDTO","description":"One obligation as this surface returns it."},"UpdateCustomerRequestDTO":{"properties":{"identifier_type":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Identifier Type"},"identifier_value":{"anyOf":[{"type":"string","maxLength":50,"minLength":1},{"type":"null"}],"title":"Identifier Value"},"customer_name":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Customer Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"country":{"anyOf":[{"type":"string","maxLength":3,"minLength":3},{"type":"null"}],"title":"Country"},"emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Emails"},"landlines":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Landlines"},"mobiles":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Mobiles","description":"The customer's mobile numbers, replacing every stored mobile. This list cannot say which number serves which channel, so a number the customer already has keeps the channels it serves and a number new to the customer is enabled for sms, whatsapp and call. Sending back the numbers a read returned is therefore a no-op. Because a new number claims every channel, adding one alongside an existing number is rejected — use 'contacts' with explicit 'channels' to give a customer several mobiles."},"contacts":{"anyOf":[{"items":{"$ref":"#/components/schemas/CustomerContactDTO"},"type":"array"},{"type":"null"}],"title":"Contacts"},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id"},"client_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Source"}},"type":"object","title":"UpdateCustomerRequestDTO","description":"Request body for partially updating a customer."},"UpdateCustomerResponseDTO":{"properties":{"id":{"type":"string","title":"Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"identifier_type":{"type":"string","title":"Identifier Type"},"identifier_value":{"type":"string","title":"Identifier Value"},"customer_name":{"type":"string","title":"Customer Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"emails":{"items":{"type":"string"},"type":"array","title":"Emails"},"mobiles":{"items":{"type":"string"},"type":"array","title":"Mobiles"},"landlines":{"items":{"type":"string"},"type":"array","title":"Landlines"},"contacts":{"items":{"$ref":"#/components/schemas/CustomerContactResponseDTO"},"type":"array","title":"Contacts"},"client_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Source"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","external_id","identifier_type","identifier_value","customer_name","last_name","country","emails","mobiles","landlines","client_source","created_at","updated_at"],"title":"UpdateCustomerResponseDTO","description":"Response body for the customer update endpoint."},"UpsertCustomerRequestDTO":{"properties":{"identifier_type":{"type":"string","minLength":1,"title":"Identifier Type"},"identifier_value":{"type":"string","maxLength":50,"minLength":1,"title":"Identifier Value"},"customer_name":{"type":"string","minLength":1,"title":"Customer Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"country":{"anyOf":[{"type":"string","maxLength":3,"minLength":3},{"type":"null"}],"title":"Country"},"emails":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Emails"},"landlines":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Landlines"},"mobiles":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Mobiles","description":"The customer's mobile numbers, replacing every stored mobile. This list cannot say which number serves which channel, so a number the customer already has keeps the channels it serves and a number new to the customer is enabled for sms, whatsapp and call. Sending back the numbers a read returned is therefore a no-op. Because a new number claims every channel, adding one alongside an existing number is rejected — use 'contacts' with explicit 'channels' to give a customer several mobiles."},"contacts":{"anyOf":[{"items":{"$ref":"#/components/schemas/CustomerContactDTO"},"type":"array"},{"type":"null"}],"title":"Contacts"},"external_id":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"External Id"},"client_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Source"}},"type":"object","required":["identifier_type","identifier_value","customer_name"],"title":"UpsertCustomerRequestDTO","description":"Request body for creating or updating a customer."},"UpsertCustomerResponseDTO":{"properties":{"id":{"type":"string","title":"Id"},"external_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Id"},"identifier_type":{"type":"string","title":"Identifier Type"},"identifier_value":{"type":"string","title":"Identifier Value"},"customer_name":{"type":"string","title":"Customer Name"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"emails":{"items":{"type":"string"},"type":"array","title":"Emails"},"mobiles":{"items":{"type":"string"},"type":"array","title":"Mobiles"},"landlines":{"items":{"type":"string"},"type":"array","title":"Landlines"},"contacts":{"items":{"$ref":"#/components/schemas/CustomerContactResponseDTO"},"type":"array","title":"Contacts"},"client_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Client Source"},"created":{"type":"boolean","title":"Created"},"created_at":{"type":"string","title":"Created At"},"updated_at":{"type":"string","title":"Updated At"}},"type":"object","required":["id","external_id","identifier_type","identifier_value","customer_name","last_name","country","emails","mobiles","landlines","client_source","created","created_at","updated_at"],"title":"UpsertCustomerResponseDTO","description":"Response body for the customer upsert endpoint."},"UpsertPaymentRequestDTO":{"properties":{"reference":{"type":"string","minLength":1,"title":"Reference","description":"Your own receipt/voucher reference for this payment. Matched against the payments already stored for this customer to decide whether this call creates a new payment or amends one."},"amount":{"anyOf":[{"type":"number","exclusiveMinimum":0.0},{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"}],"title":"Amount","description":"With obligation_reference_number, the amount applied to that obligation. Without it, the payment's total amount."},"received_date":{"type":"string","title":"Received Date","description":"ISO-8601 date the payment was received (YYYY-MM-DD). Immutable once posted: an amend naming a different date is refused rather than applied."},"obligation_reference_number":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Obligation Reference Number","description":"The reference_number of the obligation this payment settles. Omit for a payment you have not applied to anything yet."},"customer_identifier":{"anyOf":[{"$ref":"#/components/schemas/CustomerIdentifierDTO"},{"type":"null"}],"description":"The customer the payment was received from. Required when no obligation_reference_number is given; checked against the obligation's customer when both are sent."},"currency":{"anyOf":[{"type":"string","maxLength":3,"minLength":1},{"type":"null"}],"title":"Currency","description":"ISO 4217 code — defaults to the obligation's own currency, so it is required when no obligation_reference_number is given. Read only when the payment is created; it is fixed at first post, so an amend ignores it."}},"type":"object","required":["reference","amount","received_date"],"title":"UpsertPaymentRequestDTO","description":"Input DTO for creating or updating a payment, addressed by (reference, customer).\n\nShared by the single-payment upsert route and the batch route: an item's\nfate on a repeat submission is decided by whether ``reference`` already\nmatches a payment stored for the same customer. What the money settles is\nnot part of that key, so a receipt posted on account and later applied to\nan obligation stays one payment.\n\nThe customer is read from ``obligation_reference_number`` when the payment\nnames the obligation it settles, and from ``customer_identifier`` when it\ndoes not; at least one is required."},"UpsertPaymentResponseDTO":{"properties":{"payment_id":{"type":"string","title":"Payment Id"},"obligation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Obligation Id"},"reference":{"type":"string","title":"Reference"},"amount":{"type":"string","title":"Amount"},"unallocated_amount":{"type":"string","title":"Unallocated Amount"},"currency":{"type":"string","title":"Currency"},"received_date":{"type":"string","title":"Received Date"},"created":{"type":"boolean","title":"Created"},"timestamp":{"type":"string","title":"Timestamp"}},"type":"object","required":["payment_id","obligation_id","reference","amount","unallocated_amount","currency","received_date","created","timestamp"],"title":"UpsertPaymentResponseDTO","description":"Response DTO returned after creating or amending a payment.\n\n``amount`` is the payment's total, not the part this call applied:\napplying a receipt already on account moves nothing, and a payment can\ncarry allocations the caller never made."}},"securitySchemes":{"TenantAPIKey":{"type":"http","description":"Your tenant API key, sent as `Authorization: Bearer <key>`.","scheme":"bearer"}}},"tags":[{"name":"Service","description":"Confirm an API key is accepted and read the version it reaches."},{"name":"Customers","description":"Register and maintain the customers an obligation is owed by."},{"name":"Obligations","description":"Register and maintain the obligations a customer owes."},{"name":"Payments","description":"Record and maintain the payments applied against an obligation."}],"servers":[{"url":"/api"}]}