openapi: 3.1.0 info: title: End Close Bank Account Balances Records API description: REST API is used to interact with the End Close platform. license: name: MIT version: 1.0.0 servers: - url: https://api.endclose.com/v1 security: - ApiKeyAuth: [] tags: - name: Records paths: /records: get: summary: List records description: 'Returns a paginated list of records, newest first. At least one of `data_stream_key`, `reconciliation_match_id`, `reconciliation_id`, `external_id`, `import_batch_id`, or `bulk_request_id` is required. Pagination is cursor-based: pass each response''s `next_cursor` back as `cursor` until `next_cursor` is null. The legacy `offset` parameter and its `has_more`/`offset`/`total_count` response shape have been removed.' parameters: - in: query name: data_stream_key required: false schema: type: string description: Filter by data stream key - in: query name: reconciliation_match_id required: false schema: type: string description: Filter to records that belong to the given match - in: query name: reconciliation_id required: false schema: type: string description: Filter to records that have a status row in the given reconciliation - in: query name: external_id required: false schema: type: string description: Filter to records with this exact `external_id` - in: query name: import_batch_id required: false schema: type: integer description: Filter to records created by this import batch - in: query name: bulk_request_id required: false schema: type: integer description: Filter to records created by this bulk request - in: query name: created_after required: false schema: type: string format: date-time description: ISO8601 lower bound on `created_at` - in: query name: created_before required: false schema: type: string format: date-time description: ISO8601 upper bound on `created_at` - in: query name: source required: false schema: type: string enum: - csv_upload - nacha_upload - bulk_api description: Filter via the joined import batch's `source` - in: query name: status required: false schema: type: string enum: - matched - unmatched description: Filter by match status - in: query name: cursor required: false schema: type: string description: Opaque cursor from a previous response's `next_cursor`. Omit for the first page. - in: query name: limit required: false schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Paginated list of records content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Record' next_cursor: type: string nullable: true description: Opaque cursor for the next page. Null when no more pages. limit: type: integer '422': description: No scoping filter provided, or invalid `status`, `created_after`, or `created_before` tags: - Records post: summary: Create a record requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/Record' responses: '201': description: Record created content: application/json: schema: $ref: '#/components/schemas/Record' tags: - Records /records/bulk: post: summary: Bulk create records description: Submit up to 1,000 records for asynchronous processing. Returns immediately with a bulk request ID. Each record is validated and inserted independently, so some records can succeed while others fail. Use the `GET /bulk_requests/{id}` endpoint to poll for results. parameters: - in: header name: Idempotency-Key schema: type: string description: Optional idempotency key. If a bulk request with this key already exists for your environment, the existing request is returned instead of creating a new one. requestBody: required: true content: application/json: schema: type: object required: - records properties: on_conflict: type: string enum: - skip - error default: skip description: How to handle records whose `external_id` already exists in the target stream. `skip` (default) silently skips duplicates and counts them in `skipped_items`. `error` surfaces duplicates as per-row failures. Only applies to records with `external_id` set. records: type: array minItems: 1 maxItems: 1000 items: type: object required: - date - data_stream_key - amount - direction properties: date: type: string format: date description: The date of the record data_stream_key: type: string description: Key of the data stream this record belongs to amount: type: integer description: The monetary amount in cents (e.g., 1234 = $12.34) currency: type: string description: Three-letter ISO 4217 currency code (e.g. USD, EUR). **Optional — defaults to `USD` when omitted.** Case-insensitive on input. pattern: ^[A-Za-z]{3}$ default: USD direction: type: string enum: - credit - debit description: Whether this is a credit or debit description: type: string description: Optional description external_id: type: string description: Optional external identifier metadata: type: object additionalProperties: true default: {} description: Arbitrary key-value metadata responses: '202': description: Bulk request accepted for processing content: application/json: schema: $ref: '#/components/schemas/BulkRequestSummary' '400': description: Invalid `on_conflict` value content: application/json: schema: type: object properties: message: type: string '401': description: Unauthorized '422': description: Invalid request (e.g. records is not an array, empty, or exceeds 1,000 items) content: application/json: schema: type: object properties: message: type: string tags: - Records /records/{id}: get: summary: Retrieve a record description: Retrieve a single record by its unique ID. parameters: - in: path name: id required: true schema: type: string description: The unique identifier of the record responses: '200': description: Record retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Record' '401': description: Unauthorized '404': description: Record not found tags: - Records patch: summary: Update a record description: Update a record's fields. The record **must have a status of `unreconciled`**. Records that are partially or fully reconciled cannot be modified. parameters: - in: path name: id required: true schema: type: string description: The unique identifier of the record requestBody: required: true content: application/json: schema: type: object properties: amount: type: integer description: The monetary amount in cents (e.g., 1234 = $12.34) direction: type: string enum: - credit - debit description: type: string date: type: string format: date external_id: type: string metadata: type: object additionalProperties: true responses: '200': description: Record updated content: application/json: schema: $ref: '#/components/schemas/Record' '401': description: Unauthorized '404': description: Record not found '409': description: Record cannot be modified because it has already been reconciled tags: - Records delete: summary: Delete a record description: Deletes a single record by its ID. The record **must have a status of `unreconciled`**. Records that are partially or fully reconciled cannot be deleted. parameters: - in: path name: id required: true schema: type: string description: The unique identifier of the record responses: '204': description: Record deleted successfully '401': description: Unauthorized '404': description: Record not found '409': description: Record cannot be deleted because it is already reconciled or partially reconciled tags: - Records components: schemas: BulkRequestSummary: type: object description: Summary returned when a bulk request is created or retrieved via idempotency key properties: id: type: integer description: Unique identifier for the bulk request status: type: string enum: - pending - processing - completed - failed - cancelled description: Current processing status total_items: type: integer description: Number of records submitted processed_items: type: integer description: Number of records processed so far successful_items: type: integer description: Number of records successfully created failed_items: type: integer description: Number of records that failed skipped_items: type: integer description: 'Number of records skipped due to `on_conflict: skip` matching an existing `external_id`' url: type: string description: Polling URL to check progress (e.g. /v1/bulk_requests/123) Record: type: object required: - date - data_stream_key - amount - direction properties: id: type: integer amount: type: integer description: The monetary amount in cents (e.g., 1234 = $12.34) currency: type: string description: Three-letter ISO 4217 currency code (e.g. USD, EUR). **Optional on create — defaults to `USD` when omitted.** Case-insensitive on input; always returned uppercase. pattern: ^[A-Za-z]{3}$ default: USD decimal_places: type: integer description: Number of decimal places for this currency (2 for USD) default: 2 direction: type: string enum: - credit - debit description: type: string date: type: string format: date metadata: type: object additionalProperties: true default: {} external_id: type: string data_stream_key: type: string securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-KEY