# Fetching all articles from the BAS API

This guide shows how to retrieve the full set of articles from the BAS (BASTA)
public API. It covers authentication, then fetching all companies and paging
through each company's articles.

Interactive API reference (Swagger UI): https://api.bastaonline.se/swagger/index.html

## 1. Authenticate

All API requests must be authenticated with a bearer token.

Exchange your integration account's username and password for a short-lived
JWT.

```
POST https://api.bastaonline.se/v3/auth/token?username={username}&password={password}
```

Response:

```json
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires": "2026-08-13T18:30:00Z"
}
```

Send the token on subsequent requests:

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

Request a new token before `expires` passes.

Contact your BAS account manager if you don't have integration credentials.

## 2. Fetch all companies

Articles belong to a company (supplier). To fetch every article, first
collect every company ID using `GET /v3/companies`, then loop through each
company's articles (step 3).

```
GET https://api.bastaonline.se/v3/companies?page=1&pageSize=2000
Authorization: Bearer {token}
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `page` | integer | **Yes** | Page number, 1-based. |
| `pageSize` | integer | **Yes** | Results per page. Maximum **2000**. |

Response is a plain array of companies — there's no total count or page
count, so keep requesting the next page until a response comes back with
fewer items than `pageSize` (or empty):

```json
[
  {
    "id": 42,
    "companyName": "Example Supplier AB",
    "orgNo": "556000-0000",
    "address": "...",
    "postalCode": "...",
    "city": "...",
    "telephone": "...",
    "website": "...",
    "externalLink": "...",
    "externalLink2": "...",
    "bastaCompanyUrl": "https://...",
    "isByggkeramikradetMember": false,
    "activationDate": "2020-01-01T00:00:00Z",
    "lastModifiedAt": "2026-06-01T12:00:00Z"
  }
]
```

Collect the `id` of every company returned — you'll use it as `companyId` in
the next step.

### Example (pseudocode)

```python
companies = []
page = 1
page_size = 2000

while True:
    response = get(
        "https://api.bastaonline.se/v3/companies",
        params={"page": page, "pageSize": page_size},
        headers={"Authorization": f"Bearer {token}"},
    )
    batch = response.json()
    companies.extend(batch)

    if len(batch) < page_size:
        break
    page += 1
```

## 3. Fetch each company's articles

For every company ID collected in step 2, page through
`GET /v3/companies/{companyId}/articles`.

```
GET https://api.bastaonline.se/v3/companies/{companyId}/articles?page=1&pageSize=5000
Authorization: Bearer {token}
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `companyId` | integer | **Yes** (path) | Basta internal company ID, from step 2. |
| `page` | integer | **Yes** | Page number, 1-based. |
| `pageSize` | integer | **Yes** | Results per page. Maximum **5000**. |

Response is paginated, with the full article detail record per item:

```json
{
  "searchTime": 12,
  "pagination": {
    "total": 340,
    "page": 1,
    "pageSize": 5000,
    "pages": 1
  },
  "items": [
    {
      "id": 123456,
      "articleName": "Example article",
      "articleNumber": "ABC-123",
      "gtin": "07300100000000",
      "gradeId": 1,
      "grade": "ALPHA",
      "lastModified": "2026-06-01T12:00:00Z",
      "...": "..."
    }
  ]
}
```

- `pagination.total` — total number of articles for this company.
- `pagination.pages` — total number of pages at the given `pageSize`.
- `items` — the articles on this page (full `Article` records, including
  documents/certifications-related fields — see
  `GET /v3/articles/{articleId}` in the Swagger reference for the complete
  field list).

`grade`/`gradeId` values follow the BASTA grading scale: `1` = ALPHA,
`2` = BETA, `4` = DECLARED, `5` = ELECTRONICS, `21` = BETA upgraded to ALPHA,
`41` = DECLARED upgraded to ALPHA.

Loop from `page = 1` to `pagination.pages` for each company, keeping
`pageSize` fixed (5000 is the maximum).

### Example (pseudocode)

```python
articles = []

for company in companies:
    page = 1
    page_size = 5000

    while True:
        response = get(
            f"https://api.bastaonline.se/v3/companies/{company['id']}/articles",
            params={"page": page, "pageSize": page_size},
            headers={"Authorization": f"Bearer {token}"},
        )
        data = response.json()
        articles.extend(data["items"])

        if page >= data["pagination"]["pages"]:
            break
        page += 1
```

## 4. Fetch criteria areas for each article

An article's criteria areas (information, health & environmental hazards,
circularity, renewability, environmental effects, emissions & tests) are not
included in the article records from step 3 — fetch them separately per
`articleId`.

You have two options: fetch all six areas in a single call, or fetch only
the specific area(s) you need.

### Option A: All areas in one call (recommended)

```
GET https://api.bastaonline.se/v3/articles/{articleId}/criteria-areas
Authorization: Bearer {token}
```

Returns all six areas together:

```json
{
  "information": { "...": "..." },
  "healthAndEnvironmentalHazards": { "...": "..." },
  "circularity": { "...": "..." },
  "renewability": { "...": "..." },
  "environmentalEffects": { "...": "..." },
  "emissionsAndTests": { "...": "..." }
}
```

This is equivalent to calling all six endpoints in Option B and combining
the results — use this unless you specifically only need one area, since
it's a single request instead of six.

### Option B: One area at a time

Each area is also available as its own endpoint, returning just that area's
data directly (not wrapped in a `criteria-areas` object):

| Endpoint | Returns |
|---|---|
| `GET /v3/articles/{articleId}/criteria-areas/information` | `information` |
| `GET /v3/articles/{articleId}/criteria-areas/health-and-environmental-hazards` | `healthAndEnvironmentalHazards` |
| `GET /v3/articles/{articleId}/criteria-areas/circularity` | `circularity` |
| `GET /v3/articles/{articleId}/criteria-areas/renewability` | `renewability` |
| `GET /v3/articles/{articleId}/criteria-areas/environmental-effects` | `environmentalEffects` |
| `GET /v3/articles/{articleId}/criteria-areas/emissions-and-tests` | `emissionsAndTests` |

All take `articleId` as the only (path) parameter.

### Example (pseudocode)

```python
criteria_areas_by_article = {}

for article in articles:
    response = get(
        f"https://api.bastaonline.se/v3/articles/{article['id']}/criteria-areas",
        headers={"Authorization": f"Bearer {token}"},
    )
    criteria_areas_by_article[article["id"]] = response.json()
```

## 5. Fetch documents and certifications for each article

Documents and certifications are separate sub-resources, not part of
criteria areas — fetch each with its own call per `articleId`.

```
GET https://api.bastaonline.se/v3/articles/{articleId}/documents
Authorization: Bearer {token}
```

```
GET https://api.bastaonline.se/v3/articles/{articleId}/certifications?language=sv
Authorization: Bearer {token}
```

| Endpoint | Parameters | Returns |
|---|---|---|
| `GET /v3/articles/{articleId}/documents` | `articleId` (path, required) | Article documents |
| `GET /v3/articles/{articleId}/certifications` | `articleId` (path, required), `language` (query, optional, default `sv`) | Article certifications |

### Example (pseudocode)

```python
documents_by_article = {}
certifications_by_article = {}

for article in articles:
    article_id = article["id"]

    documents_by_article[article_id] = get(
        f"https://api.bastaonline.se/v3/articles/{article_id}/documents",
        headers={"Authorization": f"Bearer {token}"},
    ).json()

    certifications_by_article[article_id] = get(
        f"https://api.bastaonline.se/v3/articles/{article_id}/certifications",
        params={"language": "sv"},
        headers={"Authorization": f"Bearer {token}"},
    ).json()
```

## 6. Getting updates later (incremental sync)

Once you've done the full initial fetch (steps 2–5), don't repeat it on every
sync. Instead, periodically pull only the articles modified since your last
sync using `GET /v3/search/period`.

```
GET https://api.bastaonline.se/v3/search/period?from={lastSyncTime}&to={now}&page=1&pageSize=5000
Authorization: Bearer {token}
```

| Parameter | Type | Required | Description |
|---|---|---|---|
| `from` | date-time | **Yes** | Start of the modification window. |
| `to` | date-time | **Yes** | End of the modification window. |
| `page` | integer | **Yes** | Page number, 1-based. |
| `pageSize` | integer | **Yes** | Results per page. Maximum **5000**. |

The response has the same shape as `GET /v3/search/articles` — a
`pagination` block plus `items` — so page through it the same way (loop
`page = 1` to `pagination.pages`).

For every article `id` returned, treat it as changed: re-fetch its criteria
areas (step 4) and documents/certifications (step 5) to refresh your copy,
since the search result item doesn't include that data itself.

> **Note:** the date range is exclusive on both ends, so an article modified
> at exactly the `from` or `to` timestamp could be missed at a window
> boundary. Overlap your sync windows slightly — e.g. set `from` a few
> seconds before your last sync's `to` — to avoid gaps. Re-processing an
> article you already have is harmless.

### Example (pseudocode)

```python
from datetime import datetime, timedelta, timezone

def sync_updates(last_sync_time):
    now = datetime.now(timezone.utc)
    from_time = last_sync_time - timedelta(seconds=5)  # small overlap
    page = 1
    page_size = 5000
    updated_articles = []

    while True:
        response = get(
            "https://api.bastaonline.se/v3/search/period",
            params={"from": from_time.isoformat(), "to": now.isoformat(), "page": page, "pageSize": page_size},
            headers={"Authorization": f"Bearer {token}"},
        )
        data = response.json()
        updated_articles.extend(data["items"])

        if page >= data["pagination"]["pages"]:
            break
        page += 1

    for article in updated_articles:
        # re-fetch criteria areas, documents, certifications for article["id"]
        ...

    return now  # store as last_sync_time for the next run
```

### Per-criteria-area update endpoints

Each criteria area also has its own dedicated update endpoint:

| Endpoint |
|---|
| `GET /v3/search/period/information` |
| `GET /v3/search/period/health-and-environmental-hazards` |
| `GET /v3/search/period/circularity` |
| `GET /v3/search/period/renewability` |
| `GET /v3/search/period/environmental-effects` |
| `GET /v3/search/period/emissions-and-tests` |

They take the same `from`/`to`/`page`/`pageSize` parameters as
`/v3/search/period`, but each returns articles modified in that specific
criteria area **with that area's full data already embedded** in each item
(alongside a `criteriaAreaLastModifiedAt` field) — removing the need for a
separate step-4 call per updated article when only one area matters to your
integration.

## Notes

- All endpoints in this guide are versioned under `/v3`.
- Requests must include a valid `Authorization: Bearer` token, or they will
  be rejected with `401 Unauthorized`.