# List sync logs

`GET https://stylor.ai/api/v1/datasets/{datasetId}/sources/{sourceId}/logs`

- Authentication: private API key, sent as `Authorization: Bearer sgpt-sk-…::…`
- Rate-limit resource: `api.v1.datasets.sources.logs.list`
- Demo key: not accepted
- Web page: https://stylor.ai/guides/rest/v1/list-sync-logs

Returns the run history of a source, most recently started first. One log is
written per sync run that reached the fetch stage, whether the run was triggered
manually or by the scheduler, and logs are kept indefinitely — unlike jobs, which
are purged a week after they finish.

Each log carries its final counters, an `error` if the run failed, and the
run's line-by-line `logs` entries. Log entries on this endpoint contain only
`timestamp`, `level` and `message`.

An unknown `datasetId` or `sourceId`, or a source that belongs to a different
dataset, returns `404`. A source with no logs returns an empty list.

## Example request

```bash
curl -X GET "https://stylor.ai/api/v1/datasets/YOUR_DATASET_ID/sources/YOUR_SOURCE_ID/logs" \
  -H "Authorization: Bearer $STYLOR_API_KEY"
```

## Path parameters

- `datasetId` (string (uuid), required): The dataset the source belongs to.
- `sourceId` (string (uuid), required): The source whose run history to list.

## Query parameters

- `page` (integer): 1-based page number. Values below 1 or non-numeric values fall back to 1. [default `1`; minimum 1]
- `limit` (integer): Logs per page. Clamped to the range 1–50; non-numeric values fall back to 10. [default `10`; minimum 1; maximum 50]

## Responses

### 200

The requested page of logs.

Content type `application/json`:

- `success` (boolean, required): Always `true` on a 2xx response.
- `logs` (array<SyncLog>, required): Logs on this page, most recently started first.
  - `logId` (string (uuid), required): Unique identifier of the log. Referenced by `SyncJob.logId`. [example `"d9f1b3a5-7c2e-4d6f-8a1b-3c5e7f9a1b2d"`]
  - `sourceId` (string (uuid), required): The source that was synced. [example `"8a4d2f1e-3c5b-4e6a-9d7f-2b1c3d4e5f6a"`]
  - `organizationId` (string (uuid), required): The organization that owns the source. [example `"5b7e9c1a-2d4f-4a6b-8c1d-9e2f3a4b5c6d"`]
  - `datasetId` (string (uuid), required): The dataset the source feeds. [example `"3f1c2a9e-6b7d-4c1e-9a2b-1d4e5f6a7b8c"`]
  - `status` (string, required): `running` while the worker is still fetching or diffing; `completed` or `failed` once finished. [one of `"running"`, `"completed"`, `"failed"`]
  - `stats` (SyncStats, required): Counters for one sync run. `retrieved` is how many products the store returned; the other four describe what the run did with them in the dataset's ingest queue. Items are processed asynchronously afterwards, so `added` and `updated` count queued changes rather than items that are already searchable.
    - `retrieved` (integer, required): Products fetched from the store (capped at 25,000 per run). [minimum 0; example `1284`]
    - `added` (integer, required): Products not previously in the dataset that were queued for import. [minimum 0; example `12`]
    - `updated` (integer, required): Existing products whose store data changed and were queued for re-processing. [minimum 0; example `37`]
    - `archived` (integer, required): Dataset items whose SKU no longer appears in the store, marked archived. [minimum 0; example `3`]
    - `failed` (integer, required): Products that could not be queued. Details are in the log's `error`-level entries. [minimum 0; example `0`]
  - `error` (string): The failure reason. Present only when `status` is `failed`. [example `"Shopify API access forbidden (403)"`]
  - `startedAt` (string (date-time), required): When the run began. Lists are sorted by this field, newest first. [example `"2026-09-11T02:58:40.005Z"`]
  - `completedAt` (string (date-time)): When the run finished. Absent while `running`. [example `"2026-09-11T03:00:12.418Z"`]
  - `createdAt` (string (date-time), required): When the log record was created. [example `"2026-09-11T02:58:40.006Z"`]
  - `updatedAt` (string (date-time), required): When the log last changed. [example `"2026-09-11T03:00:12.418Z"`]
  - `logs` (array<SyncLogEntry>, required): Chronological entries written during the run. Empty array if none.
    - `timestamp` (string (date-time), required): When the entry was written. [example `"2026-09-11T02:59:31.880Z"`]
    - `level` (string, required): Severity. Treat any value outside this list (for example `warn`, which the completion entry uses when items failed) as `warning`. [one of `"info"`, `"success"`, `"warning"`, `"error"`; example `"info"`]
    - `message` (string, required): Human-readable progress or error text. [example `"Retrieved 1284 products"`]
- `total` (integer, required): Total number of logs for the source.
- `page` (integer, required): The page that was returned (1-based).
- `limit` (integer, required): Page size that was applied after clamping.

Example:

```json
{
  "success": true,
  "logs": [
    {
      "logId": "d9f1b3a5-7c2e-4d6f-8a1b-3c5e7f9a1b2d",
      "sourceId": "8a4d2f1e-3c5b-4e6a-9d7f-2b1c3d4e5f6a",
      "organizationId": "5b7e9c1a-2d4f-4a6b-8c1d-9e2f3a4b5c6d",
      "datasetId": "3f1c2a9e-6b7d-4c1e-9a2b-1d4e5f6a7b8c",
      "status": "completed",
      "stats": {
        "retrieved": 1284,
        "added": 12,
        "updated": 37,
        "archived": 3,
        "failed": 0
      },
      "startedAt": "2026-09-11T02:58:40.005Z",
      "completedAt": "2026-09-11T03:00:12.418Z",
      "createdAt": "2026-09-11T02:58:40.006Z",
      "updatedAt": "2026-09-11T03:00:12.418Z",
      "logs": [
        {
          "timestamp": "2026-09-11T02:58:40.005Z",
          "level": "info",
          "message": "Sync started by worker v1-sync-9c0f1a2b-3d4e-4f5a-8b6c-7d8e9f0a1b2c"
        },
        {
          "timestamp": "2026-09-11T02:59:31.880Z",
          "level": "info",
          "message": "Retrieved 1284 products"
        },
        {
          "timestamp": "2026-09-11T03:00:01.412Z",
          "level": "info",
          "message": "Processing batch 1/3 (500/1284 ops)"
        },
        {
          "timestamp": "2026-09-11T03:00:12.417Z",
          "level": "info",
          "message": "Sync completed: 12 added, 37 updated, 3 archived, 0 failed"
        }
      ]
    },
    {
      "logId": "2b8d4f6a-0c1e-4a3b-9d5f-7e9a1b3c5d7f",
      "sourceId": "8a4d2f1e-3c5b-4e6a-9d7f-2b1c3d4e5f6a",
      "organizationId": "5b7e9c1a-2d4f-4a6b-8c1d-9e2f3a4b5c6d",
      "datasetId": "3f1c2a9e-6b7d-4c1e-9a2b-1d4e5f6a7b8c",
      "status": "failed",
      "stats": {
        "retrieved": 0,
        "added": 0,
        "updated": 0,
        "archived": 0,
        "failed": 0
      },
      "error": "Shopify API access forbidden (403)",
      "startedAt": "2026-09-10T02:58:39.210Z",
      "completedAt": "2026-09-10T02:58:40.988Z",
      "createdAt": "2026-09-10T02:58:39.211Z",
      "updatedAt": "2026-09-10T02:58:40.988Z",
      "logs": [
        {
          "timestamp": "2026-09-10T02:58:39.210Z",
          "level": "info",
          "message": "Sync started by worker v1-sync-4a6c8e0b-2d4f-4a6c-8e0b-2d4f6a8c0e2b"
        },
        {
          "timestamp": "2026-09-10T02:58:40.987Z",
          "level": "error",
          "message": "Sync failed: Shopify API access forbidden (403)"
        }
      ]
    }
  ],
  "total": 2,
  "page": 1,
  "limit": 10
}
```

## Errors

Every error is a JSON object with an `error` string. Match on the status code; the message is written for people.

| Status | Message | When |
| --- | --- | --- |
| 404 | Data source not found or access denied | No source with that `sourceId` belongs to the organization that owns the key, or the source belongs to a different `datasetId` than the path. |

### Shared authentication, quota and rate-limit errors

| Status | Message | When |
| --- | --- | --- |
| 401 | API key missing. | The `Authorization` header is absent, is not `Bearer <key>`, or the key does not have three dash-separated segments. |
| 401 | Authentication failed: public key is invalid or not recognized. | A public-key endpoint was called with a key that does not exist. |
| 401 | Authentication failed: private key provided instead of a public key. | A public-key endpoint was called with an `sgpt-sk-…` key. |
| 401 | Authentication failed: token is not recognized. | A private-key endpoint was called with a key whose lookup segment does not exist. |
| 401 | Authentication failed: private key is invalid. | A private-key endpoint was called with a key whose secret does not match. |
| 401 | Authentication failed: public key provided instead of a private key. | A private-key endpoint was called with an `sgpt-pk-…` key. |
| 401 | Authentication failed: bearer token format is invalid. | A private key was sent without its `::` lookup segment. |
| 401 | Authentication failed: API key could not be read. Regenerate the key. | The key's embedded organization data could not be decrypted or parsed. |
| 403 | Demo mode is not available for private-key routes. Demos are only supported with public API keys. | The demo key was sent to a private-key endpoint. |
| 403 | Demo mode is not supported on this API route. Check the documentation for available demo endpoints. | The demo key was sent to a public-key endpoint that opts out of demo mode. |
| 403 | No active subscription for this organization. | The organization that owns the key has no active subscription. |
| 403 | No quota found for organization "<organizationId>". | The organization has no quota record for the current billing period. |
| 403 | No entitlement for "<resource>". | The plan's quota template has no entry for this endpoint. |
| 403 | Access denied to "<resource>". | The plan's quota template turns this endpoint off. |
| 429 | Monthly quota exceeded for "<resource>" (<quota>/<quota> used). | The billing-period quota for this endpoint is exhausted. `Retry-After` gives the seconds until the period resets. |
| 429 | RPM limit exceeded for "<resource>" (<used>/<limit> used). | More than the allowed requests per minute were sent. `Retry-After` is 60. |
| 500 | Internal server error. | An unexpected failure on the server. The message is always this string; details are logged, never returned. Quota consumed by the request is refunded, as it is for every 5xx response. |
