# Authenticate and make your first request

Source: https://help.businessradar.com/api/guides/authenticate-and-make-your-first-request
Updated: 2026-09-17

Everything in the Business Radar API sits behind one API key and one header.
This guide gets you from "no key" to a successful response, and shows what a
failure looks like so you can tell an expired key from a typo in a path.

## Before you start

- **An API key.** Request one from [support@businessradar.com](mailto:support@businessradar.com).
- **A place to keep it.** The key is a secret — it belongs in an environment
  variable on a server, never in browser code or a committed file.
- **Optionally, a client library.** Python, TypeScript and PHP clients are
  listed on [Client libraries](/api/sdks); each reads `BUSINESSRADAR_API_KEY`
  from the environment on its own.

## Step 1: Get a key and store it

Keys are issued by hand: email
[support@businessradar.com](mailto:support@businessradar.com) and say which
environment you need one for. The schema names the same address, so there is no
self-service key page to look for.

Once you have the key, export it in your shell for the examples below, and use
your usual secret manager in production.

```bash
export BUSINESSRADAR_API_KEY="your-api-key"
```

## Step 2: Send it on every request

The base URL is `https://api.businessradar.com` and every endpoint lives under
`/ext/v3`. Authentication is a single header:

```http
Authorization: Bearer <token>
```

That is the exact form the OpenAPI schema documents for the `tokenAuth` scheme
(`type: http`, `scheme: bearer`). There is no cookie, no session and no refresh
step — the same header goes on every call.

## Step 3: Make a call

`GET /ext/v3/portfolios` is the cheapest authenticated endpoint in the API — a
short list, no filters required — which makes it the fastest way to prove a key
works.

**curl**

```bash
curl -sS https://api.businessradar.com/ext/v3/portfolios \
  -H "Authorization: Bearer $BUSINESSRADAR_API_KEY"
```

**python**

```python

from businessradar import BusinessRadar

client = BusinessRadar(api_key=os.environ["BUSINESSRADAR_API_KEY"])

page = client.portfolios.list()
for portfolio in page.results:
    print(portfolio.external_id, portfolio.name)
```

**typescript**

```ts

const client = new BusinessRadar({ apiKey: process.env['BUSINESSRADAR_API_KEY'] });

const page = await client.portfolios.list();
for (const portfolio of page.results) {
  console.log(portfolio.external_id, portfolio.name);
}
```

**php**

```php
<?php

use Businessradar\Client;

$client = new Client(apiKey: getenv('BUSINESSRADAR_API_KEY'));

$page = $client->portfolios->list();
foreach ($page->getItems() as $portfolio) {
    echo $portfolio->external_id, ' ', $portfolio->name, PHP_EOL;
}
```

### What you get

Every list endpoint returns the same envelope: the rows in `results`, a
`total_results` count for the whole query, and a `next_key` cursor that is
`null` on the last page.

```json
{
  "total_results": 2,
  "next_key": null,
  "results": [
    {
      "external_id": "6f1d0b8e-3a1e-4d25-9a54-0b1b2f2f8c10",
      "name": "Suppliers EU",
      "customer_reference": "erp-suppliers-eu",
      "default_permission": "view_only"
    },
    {
      "external_id": "b3b1f8f3-6a15-4a56-8f9a-2c3e9a1f7d44",
      "name": "Key accounts",
      "customer_reference": null,
      "default_permission": "write"
    }
  ]
}
```

To walk a long list, pass the `next_key` you were given back as a query
parameter and repeat until it comes back `null`. The clients do this for you —
iterating the page object fetches further pages as needed.

**curl**

```bash
curl -sS "https://api.businessradar.com/ext/v3/portfolios?next_key=eyJvZmZzZXQiOjUwfQ" \
  -H "Authorization: Bearer $BUSINESSRADAR_API_KEY"
```

**python**

```python
# Iterating the page fetches the next page automatically.
for portfolio in client.portfolios.list():
    print(portfolio.name)
```

**typescript**

```ts
for await (const portfolio of client.portfolios.list()) {
  console.log(portfolio.name);
}
```

**php**

```php
<?php

foreach ($client->portfolios->list()->pagingEachItem() as $portfolio) {
    echo $portfolio->name, PHP_EOL;
}
```

## Step 4: Handle the failures

Errors come back as JSON with the same shapes everywhere in the API.

| Status | Meaning | Body |
| --- | --- | --- |
| 401 | Authentication credentials were not provided, or the key is wrong. | `{"detail": "..."}` |
| 403 | The key is valid but not permitted to do this. | `{"detail": "..."}` |
| 404 | No such resource — check the `external_id`. | `{"detail": "..."}` |
| 400 | The request body failed validation. | `{"errors": {...}, "non_field_errors": [...]}` |
| 429 | You are sending requests faster than your plan allows. | `{"detail": "..."}` |
| 500 | Something broke on our side. Contact support with the time of the call. | `{"detail": "..."}` |

A missing or malformed header looks like this:

```bash
curl -i -sS https://api.businessradar.com/ext/v3/portfolios
```

```json
{
  "detail": "Authentication credentials were not provided."
}
```

A rejected body — say a compliance check with no company and no entities —
returns the field-level shape instead:

```json
{
  "errors": {
    "name": ["This field may not be blank."]
  },
  "non_field_errors": []
}
```

### Rate limits and retries

A 429 means you have exceeded your request rate; the body carries a `detail`
message and you should back off before trying again.

**The official clients already handle this for you.** All three retry
connection errors, 408, 409, 429 and 5xx responses twice by default, with
exponential back-off and jitter, and they honour a `Retry-After` (or
`retry-after-ms`) header when the response carries one. A `RateLimitError` /
`RateLimitException` therefore only reaches your code once the retries are
spent. Change the budget with `max_retries` (Python), `maxRetries` (TypeScript
and PHP), per client or per call:

**python**

```python
client.with_options(max_retries=5).portfolios.list()
```

**typescript**

```ts
// Request options are the second argument, after the query.
await client.portfolios.list({}, { maxRetries: 5 });
```

**php**

```php
<?php

$client->portfolios->list(requestOptions: ['maxRetries' => 5]);
```

Retrying by hand on top of that only helps for the long pauses — a queue that
backs off for minutes, not a loop that hammers the same second.

**curl**

```bash
# -f makes curl exit non-zero on 4xx/5xx; -i shows the status line and headers.
curl -fi -sS https://api.businessradar.com/ext/v3/portfolios \
  -H "Authorization: Bearer $BUSINESSRADAR_API_KEY" || echo "request failed"
```

**python**

```python

try:
    page = client.portfolios.list()
except businessradar.AuthenticationError:
    print("The API key was rejected (401).")
except businessradar.RateLimitError:
    print("429 — back off and retry later.")
except businessradar.APIStatusError as error:
    print(error.status_code, error.response)
except businessradar.APIConnectionError as error:
    print("The server could not be reached:", error.__cause__)
```

**typescript**

```ts
const page = await client.portfolios.list().catch((error) => {
  if (error instanceof BusinessRadar.APIError) {
    console.log(error.status, error.name);
    return undefined;
  }
  throw error;
});
```

**php**

```php
<?php

use Businessradar\Core\Exceptions\APIConnectionException;
use Businessradar\Core\Exceptions\APIStatusException;
use Businessradar\Core\Exceptions\RateLimitException;

try {
    $page = $client->portfolios->list();
} catch (RateLimitException $e) {
    echo '429 — back off and retry later.', PHP_EOL;
} catch (APIStatusException $e) {
    echo $e->getMessage(), PHP_EOL;
} catch (APIConnectionException $e) {
    echo 'The server could not be reached.', PHP_EOL;
}
```

## The whole thing

Every step above in one file: read the key, walk every page of portfolios, and
fail loudly on an authentication problem.

**curl**

```bash
#!/usr/bin/env bash
set -euo pipefail

: "${BUSINESSRADAR_API_KEY:?export your API key first}"
BASE=https://api.businessradar.com/ext/v3

next_key=""
while :; do
  url="$BASE/portfolios"
  [ -n "$next_key" ] && url="$url?next_key=$next_key"

  response=$(curl -fsS "$url" \
    -H "Authorization: Bearer $BUSINESSRADAR_API_KEY") || {
      echo "request failed — check the key and try again" >&2
      exit 1
    }

  echo "$response" | jq -r '.results[] | "\(.external_id)\t\(.name)"'

  next_key=$(echo "$response" | jq -r '.next_key // empty')
  [ -z "$next_key" ] && break
done
```

**python**

```python

from businessradar import BusinessRadar

client = BusinessRadar(api_key=os.environ["BUSINESSRADAR_API_KEY"])

try:
    # The client follows next_key on its own when you iterate the pager.
    for portfolio in client.portfolios.list():
        print(portfolio.external_id, portfolio.name)
except businessradar.AuthenticationError:
    sys.exit("The API key was rejected (401). Check BUSINESSRADAR_API_KEY.")
except businessradar.RateLimitError:
    sys.exit("429 — back off and retry later.")
except businessradar.APIStatusError as error:
    sys.exit(f"{error.status_code}: {error.response}")
```

**typescript**

```ts

const client = new BusinessRadar({ apiKey: process.env['BUSINESSRADAR_API_KEY'] });

try {
  // for await walks every page; no cursor bookkeeping needed.
  for await (const portfolio of client.portfolios.list()) {
    console.log(portfolio.external_id, portfolio.name);
  }
} catch (error) {
  if (error instanceof BusinessRadar.APIError) {
    console.error(error.status, error.name);
    process.exit(1);
  }
  throw error;
}
```

**php**

```php
<?php

require 'vendor/autoload.php';

use Businessradar\Client;
use Businessradar\Core\Exceptions\APIStatusException;

$client = new Client(apiKey: getenv('BUSINESSRADAR_API_KEY'));

try {
    // pagingEachItem() requests the next page as you consume the current one.
    foreach ($client->portfolios->list()->pagingEachItem() as $portfolio) {
        echo $portfolio->external_id, ' ', $portfolio->name, PHP_EOL;
    }
} catch (APIStatusException $e) {
    fwrite(STDERR, $e->getMessage() . PHP_EOL);
    exit(1);
}
```

## Next steps

- [Run a compliance check on a company](/api/guides/run-a-compliance-check) — the
  first thing most integrations do with a key.
- [Register a portfolio and receive the right news](/api/guides/monitor-a-portfolio-for-news) —
  bulk registration, webhooks and filtered article feeds.
- Reference: [Portfolios](/api#tag/portfolios), [Companies](/api#tag/companies),
  [Compliance](/api#tag/compliance), [Articles](/api#tag/articles),
  [Webhooks](/api#tag/webhooks).
