openapi: 3.1.0 info: title: End Close Bank Account Balances Bulk Requests 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: Bulk Requests paths: /bulk_requests: get: summary: List bulk requests description: Cursor-paginated list of bulk requests in your environment, ordered by `created_at` descending. Filter by data stream, status, or source. parameters: - in: query name: data_stream_key schema: type: string description: Filter to bulk requests targeting this data stream - in: query name: status schema: type: string enum: - pending - processing - completed - failed - cancelled - in: query name: source schema: type: string description: Filter by request source (e.g. `bulk_api`, `csv`, `nacha`) - in: query name: created_after schema: type: string format: date-time description: ISO8601 lower bound on `created_at` - in: query name: created_before schema: type: string format: date-time description: ISO8601 upper bound on `created_at` - in: query name: cursor schema: type: string description: Opaque cursor returned by a previous call's `next_cursor`. Omit for the first page. - in: query name: limit schema: type: integer minimum: 1 maximum: 200 default: 50 responses: '200': description: Paginated bulk requests content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/BulkRequest' next_cursor: type: string nullable: true description: Pass back as `cursor` to fetch the next page. Null when no more pages. limit: type: integer '401': description: Unauthorized '422': description: Invalid `status`, `created_after`, or `created_before` tags: - Bulk Requests /bulk_requests/{id}: get: summary: Get bulk request status description: Poll the status of an asynchronous bulk request. Returns the bulk request payload plus paginated per-row results. Results grow as processing continues, so only non-pending items are included. parameters: - in: path name: id required: true schema: type: string description: The bulk request ID returned from POST /records/bulk - in: query name: limit schema: type: integer minimum: 1 maximum: 200 default: 50 description: Number of result items to return - in: query name: offset schema: type: integer default: 0 description: Pagination offset for result items responses: '200': description: Bulk request payload with paginated results content: application/json: schema: allOf: - $ref: '#/components/schemas/BulkRequest' - type: object properties: results: type: object properties: data: type: array items: type: object properties: index: type: integer description: Zero-based position in the original request array status: type: string enum: - successful - failed - skipped record_id: type: integer description: ID of the created record. Only present when status is successful error: type: string description: Error message. Only present when status is failed has_more: type: boolean offset: type: integer '401': description: Unauthorized '404': description: Bulk request not found tags: - Bulk Requests /bulk_requests/{id}/cancel: post: summary: Cancel a bulk request description: Sets `cancellation_requested_at` so the running job exits cleanly to `cancelled` on its next heartbeat. Idempotent — re-cancelling just bumps the timestamp. parameters: - in: path name: id required: true schema: type: string responses: '202': description: Cancellation requested content: application/json: schema: $ref: '#/components/schemas/BulkRequest' '401': description: Unauthorized '404': description: Bulk request not found '409': description: Bulk request is not in a cancellable status (must be `pending` or `processing`) content: application/json: schema: type: object properties: error: type: string tags: - Bulk Requests /bulk_requests/{id}/retry: post: summary: Retry a bulk request description: Creates a new bulk request that re-runs only the parent's failed and skipped items. The new request's `parent_bulk_request_id` points at the original. parameters: - in: path name: id required: true schema: type: string description: The ID of the failed or cancelled bulk request to retry responses: '202': description: Retry created and enqueued. Returns the **new** bulk request, not the parent. content: application/json: schema: $ref: '#/components/schemas/BulkRequest' '401': description: Unauthorized '404': description: Bulk request not found '409': description: Bulk request is not in a retryable status (must be `failed` or `cancelled`) content: application/json: schema: type: object properties: error: type: string tags: - Bulk Requests components: schemas: BulkRequest: type: object description: Full bulk request payload, returned by show, list, cancel, and retry endpoints. properties: id: type: integer status: type: string enum: - pending - processing - completed - failed - cancelled description: pending = queued, processing = records being inserted, completed = all rows processed, failed = job errored, cancelled = cancellation honored on a heartbeat source: type: string nullable: true description: Origin of the request (e.g. `bulk_api`, `csv`, `nacha`) total_items: type: integer processed_items: type: integer successful_items: type: integer failed_items: type: integer skipped_items: type: integer description: 'Records skipped due to `on_conflict: skip` matching an existing `external_id`' progress: type: integer description: processed_items / total_items as a percentage (0-100) filename: type: string nullable: true description: Original filename for CSV/NACHA uploads. Null for API-submitted requests. created_at: type: string format: date-time started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true last_progress_at: type: string format: date-time nullable: true description: Heartbeat timestamp from the worker; used to detect stuck jobs cancellation_requested_at: type: string format: date-time nullable: true description: Set by POST /bulk_requests/{id}/cancel; the worker exits at its next heartbeat parsing: type: boolean description: True while a CSV/NACHA upload is still being parsed (before rows are enqueued) stuck: type: boolean description: True if the request has been `processing` for more than 5 minutes without a heartbeat error_message: type: string nullable: true error_details: type: object additionalProperties: true description: Free-form structured error context (e.g. row indices, validation errors) parent_bulk_request_id: type: integer nullable: true description: If this request is a retry, the ID of the original (parent) bulk request on_conflict: type: string enum: - skip - error description: Conflict policy applied when an `external_id` already exists in the stream data_stream_id: type: integer nullable: true description: Data stream the request targets, when applicable securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-KEY