# Get a feed's sync status `GET /catalog/v1/{catalog_id}/sources/feeds/{feed_id}/sync-status` Return whether the feed is being processed right now, what its last run did, and when it was checked. `processing` answers what is happening to the feed now, where `last_run` answers what last happened to it; a skipped run does not replace it, and moves only `last_checked_at`, unless every run of the feed was skipped. A feed that has never run carries a `status` of `never_run` and a null `last_run`. ## Request example ```bash curl --request GET \ --url 'https://api.eu1.luigisbox.ai/catalog/v1//sources/feeds//sync-status' \ --header 'Authorization: Bearer ' ``` ## Path Parameters | Name | Type | Required | Description | Constraints | |---|---|---|---|---| | `catalog_id` | `string` | Yes | Catalog Id | — | | `feed_id` | `integer` | Yes | The feed's `feed_id`. | >= 1; <= 9007199254740991 | ## Responses | Status | Description | Content | |---|---|---| | `200` | Successful Response | application/json: object | | `401` | Missing or invalid credentials | application/json: object | | `403` | Authenticated but not permitted | application/json: object | | `404` | Resource not found | application/json: object | | `422` | Request validation failed | application/json: object | | `503` | Service temporarily unavailable | application/json: object | ### 200 Successful Response #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `feed_id` | `integer` | Yes | The feed this describes. | — | | `last_checked_at` | `anyOf(string, null)` | Yes | When the feed's newest run of any kind began, skipped runs included, so a feed checked a minute ago and found unchanged shows it. Null when the feed has never run. | — | | `last_run` | `anyOf(object, null)` | Yes | The feed's newest run that was not skipped, or its newest skipped run when every run was skipped; null when the feed has never run. Every run, skipped ones included, is listed by `sync-runs`. | — | | `last_run.catalog_id` | `string` | Yes | Catalog the run belongs to. | — | | `last_run.created_at` | `string` | Yes | When the run was recorded. | format: date-time | | `last_run.detection` | `anyOf(object, null)` | Yes | What the run's format detection found at the beginning of the file. The keys are named like the feed's own `format`, `item_tag`, `json_path` and `csv_delimiter` settings, so a difference from what is configured shows. Recorded on the runs `items_scanned` is; null when the run did not record it or the file was empty. | — | | `last_run.detection.confident` | `boolean` | Yes | Whether the detection was unambiguous; always `false` for `unknown`. | — | | `last_run.detection.csv_delimiter` | `anyOf(string, null)` | Yes | For `csv`, the character separating the columns; null for other formats. | — | | `last_run.detection.format` | `string` | Yes | The format the beginning of the file was read as. `unknown` when that part held nothing but whitespace; a file that begins like neither XML nor JSON is reported as `csv`, usually with `confident` false. More formats may be added. | allowed values: xml, csv, json, json_lines, unknown | | `last_run.detection.item_tag` | `anyOf(string, null)` | Yes | For `xml`, the repeating element the items appeared in; null when none was found, and for other formats. | — | | `last_run.detection.json_path` | `anyOf(string, null)` | Yes | For `json`, the dotted path to the items array when the file wraps it in an object; null when the array is the whole file or none was found, and for other formats. | — | | `last_run.error_message` | `anyOf(string, null)` | Yes | Human-readable detail of why the run failed, or of the items the catalog rejected. | — | | `last_run.etag` | `anyOf(string, null)` | Yes | The `ETag` the feed's server sent when the run checked the file for changes, verbatim, quotes included, or null when it sent none. Recorded on the runs `items_scanned` is, and on a run skipped because the file had not changed: after a `304` it is the value the run sent, since a `304` need not repeat it; after a `200` that matched, the one the server sent, null when the match was on `Last-Modified` alone. Null when the run did not record it. | — | | `last_run.feed_id` | `integer` | Yes | Feed the run processed. | — | | `last_run.fingerprints` | `boolean` | Yes | Whether per-item fingerprints were honoured; false means every item was resent. | — | | `last_run.finished_at` | `anyOf(string, null)` | Yes | When the run finalized; null while it is still running. | — | | `last_run.force` | `boolean` | Yes | Whether the run ignored the unchanged-feed check and preempted any in-flight run. | — | | `last_run.generation` | `anyOf(string, null)` | Yes | Generation stamped on the items this run sent. | — | | `last_run.items_no_identity` | `anyOf(integer, null)` | Yes | Items dropped for lacking an identity value. A non-zero count suppresses deletes for the run. | — | | `last_run.items_rejected` | `anyOf(integer, null)` | Yes | Items sent that the catalog rejected one by one; `error_message` names the most common reasons. 0 on a run where every item sent was accepted, non-zero on every `partial` run and on a `failed` run whose every item was rejected. Null when the run did not record it, for example while it is in flight, or when it stopped before its counts were reported after a timeout or an interruption. | — | | `last_run.items_scanned` | `anyOf(integer, null)` | Yes | Records found in the file: every item the run read from it, whatever then became of it. At least `items_sent` + `items_skipped` + `items_no_identity`; the difference is records that could not be read as an item. Recorded on a run that read the file: `succeeded`, `partial`, or `failed` with item counts; null when the run did not record it. | — | | `last_run.items_sent` | `anyOf(integer, null)` | Yes | Items submitted to the catalog; null while the run is in flight, on a skipped run, and on a failed run that ended before its counts were reported — one that failed before sending, timed out, was interrupted or failed part-way. | — | | `last_run.items_skipped` | `anyOf(integer, null)` | Yes | Items left untouched because their fingerprint was unchanged; they stay in the catalog. | — | | `last_run.last_modified` | `anyOf(string, null)` | Yes | The `Last-Modified` the feed's server sent when the run checked the file for changes, in HTTP-date format rather than ISO-8601. Recorded and null in the same cases as `etag`; on a run skipped after a `200` that matched, always the one the server sent. | — | | `last_run.started_at` | `string` | Yes | When the run started; a run is recorded as it starts. | format: date-time | | `last_run.status` | `string` | Yes | Lifecycle status of the run. | allowed values: running, succeeded, partial, failed, skipped | | `last_run.status_reason` | `anyOf(string, null)` | Yes | Why the run ended as it did. More reasons may be added. | — | | `last_run.sync_id` | `anyOf(string, null)` | Yes | The sync this run belongs to, shared by every run that sync started: `lbsy_` followed by ten lowercase Crockford base32 characters. Null for a run recorded without one, and for a run recorded before sync identifiers took this form. | — | | `last_run.sync_run_id` | `string` | Yes | The run's identifier: `lbsr_` followed by ten lowercase Crockford base32 characters. Every run has one, and it does not change. It is random, so it says nothing about when the run happened or how many runs came before it; runs are listed by `started_at`. | /^lbsr_[0-9a-hjkmnp-tv-z]{10}$/ | | `last_run.trigger` | `string` | Yes | Whether the run came from the schedule or a manual request. | allowed values: schedule, manual | | `last_run.triggered_by` | `anyOf(string, null)` | Yes | Subject that requested a manual run; null for a scheduled one. | — | | `processing` | `anyOf(boolean, null)` | Yes | Whether a run is processing the feed right now. Null when that cannot be read at the moment; the rest of the answer does not depend on it. | — | | `status` | `string` | Yes | The status of `last_run`, or `never_run` when the feed has never run. A check that found the file unchanged or the feed already being processed is recorded as a `skipped` run and does not change this: a failure stays `failed` until a later run does work, and a run in progress reads `running` until it finishes. `skipped` only when every run of the feed was skipped. | allowed values: running, succeeded, partial, failed, skipped, never_run | ##### Response example ```json { "feed_id": 1, "last_checked_at": "2026-04-15T12:00:00Z", "last_run": { "catalog_id": "example", "created_at": "2026-04-15T12:00:00Z", "detection": { "confident": true, "csv_delimiter": "example", "format": "xml", "item_tag": "example", "json_path": "example" }, "error_message": "example", "etag": "example", "feed_id": 1, "fingerprints": true, "finished_at": "2026-04-15T12:00:00Z", "force": true, "generation": "2489e9ad-2ee2-4e00-8ec9-32d5f69181c0", "items_no_identity": 1, "items_rejected": 1, "items_scanned": 1, "items_sent": 1, "items_skipped": 1, "last_modified": "example", "started_at": "2026-04-15T12:00:00Z", "status": "running", "status_reason": "timeout", "sync_id": "example", "sync_run_id": "example", "trigger": "schedule", "triggered_by": "example" }, "processing": true, "status": "running" } ``` ### 401 Missing or invalid credentials #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 403 Authenticated but not permitted #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 404 Resource not found #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 422 Request validation failed #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ``` ### 503 Service temporarily unavailable #### application/json Schema: `object` ##### Attributes | Attribute | Type | Required | Description | Constraints | |---|---|---|---|---| | `exception_details` | `object` | No | Machine-readable error context; empty object when there is nothing to add. | — | | `exception_details.error_code` | `string` | No | Stable machine code naming why a well-formed request was refused: snake_case, prefixed by its domain — e.g. `filter_unknown_field` or `cursor_mismatch`. Safe to branch on; `reason` is not. Absent when the status code says all there is to say. | — | | `exception_details.validation_errors` | `Array` | No | Every field-level problem found in the request, when validation is what failed. Absent when the rejection is not about a specific value. | — | | `exception_details.validation_errors[].loc` | `Array` | Yes | Path to the offending value from the request root, e.g. `["body", "filters", 0, "operator"]`. | — | | `exception_details.validation_errors[].msg` | `string` | Yes | What is wrong with the value at `loc`. | — | | `exception_details.validation_errors[].type` | `string` | Yes | Stable machine code for the problem, e.g. `missing` or `string_too_short`. | — | | `exception_details.*` | `any` | No | Additional property. | — | | `reason` | `string` | Yes | Human-readable, actionable error message. | — | | `request_id` | `anyOf(string, null)` | No | Request correlation ID (matches the X-Request-Id response header). | — | ##### Response example ```json { "exception_details": { "error_code": "example", "validation_errors": [ { "loc": [ "example" ], "msg": "example", "type": "example" } ] }, "reason": "example", "request_id": "example" } ```