> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.deel.wtf/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.deel.wtf/_mcp/server.

# Knowledge Hub API

> Retrieve country guide content segmented by audience and Deel product for use in partner integrations

The Knowledge Hub API returns country-specific content, such as employment rules, rates, and how local requirements apply. A single endpoint serves this content. Partner integrations typically use it to show a country reference in their own hiring or HR workflow.
This guide covers the request shape, the response structure, and the rendering rules required to display the content correctly.

## What the API provides

One endpoint serves the entire guide:

| Method | Path                                          |
| ------ | --------------------------------------------- |
| `GET`  | `/knowledge-hub/country-guide/{country_code}` |

Everything you render comes from the `fields` array on each category. Fields carry plain text and rich HTML, and hold the substantive guide content, such as a tax rate, a notice period, or an explanation of how deductions work.

### What the API does not provide

Before designing an integration with the Knowledge Hub API please note the following:

* **Article bodies**: The endpoint does not return the full Knowledge Hub article content.
* **A browsable article list**: Category payloads include an `articles` array, but it holds metadata only. There is no body and no URL, so entries cannot be opened or linked to.

> **Note**
>
> If your product requires article content, contact your Deel representative.

## Prerequisites

Three things are required before your first call:

* **A Deel access token**: Either an OAuth 2.0 token or a personal access token. See [Authentication](/api/authentication) and [OAuth 2.0](/api/oauth).
* **The `knowledge-hub:read` scope**: OAuth 2.0 tokens must carry this scope.
* **A country code**: The endpoint requires an ISO 3166-1 alpha-2 code.

## Make a request

The `country_code` path parameter is the only parameter defined in the API reference.

| Parameter      | Type     | Required | Description                                       |
| -------------- | -------- | -------- | ------------------------------------------------- |
| `country_code` | `string` | Yes      | ISO 3166-1 alpha-2 country code, for example `BR` |

A request for the Brazilian guide, narrowed to employers using EOR, looks like this:

**`cURL`**

```bash cURL
curl -G "https://api.letsdeel.com/rest/knowledge-hub/country-guide/BR" \
  -H "Authorization: Bearer $DEEL_ACCESS_TOKEN" \
  -H "X-Version: 2026-01-01" \
  --data-urlencode "audiences=client" \
  --data-urlencode "contract_types=eor"
```

It is important to apply a request filter. If no filter is applied, all results will be returned: all guides for all audiences and all products for all employers. To avoid large response payloads, make an unfiltered request to inspect the response shape, then apply filters to refine.

## Filter the response

Two optional query parameters narrow what the endpoint returns. Omitting one returns every value for it.

| Query parameter  | What it does                                                                                                      |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| `audiences`      | Returns only the audiences you name, such as `client` or `contractor`. Applies to any request                     |
| `contract_types` | Returns only the Deel products you name within the `client` audience, such as `eor`. Ignored for worker audiences |

### Passing multiple values

To submit multiple values, repeat the parameter for each value, as shown below. Comma-separated values will be treated as a single unrecognized value.

```bash
# Correct
?audiences=client&audiences=eorEmployee

# Incorrect: silently returns every audience
?audiences=client,eorEmployee
```

### Invalid filter values

A value the endpoint does not recognize is ignored rather than returning an error. If every value you send is invalid, the filter empties and defaults to returning everything.

> **Note**
>
> If a response is substantially larger than you expect, check your filter values for typographical errors.

### Contract type scope

Contract types describe which Deel products an employer uses, so they carry no meaning for worker audiences and are ignored there. A request for `audiences=eorEmployee&contract_types=gp` returns the complete `eorEmployee` guide, unfiltered.

### Audience values

Pass any of these values in `audiences`:

| Value          | Applies to                                                                        |
| -------------- | --------------------------------------------------------------------------------- |
| `client`       | The employing organization. The only audience that also branches by contract type |
| `eorEmployee`  | A worker employed through Deel Employer of Record                                 |
| `gpEmployee`   | A worker paid through Deel Global Payroll                                         |
| `hrisEmployee` | A worker managed in Deel HR                                                       |
| `peoEmployee`  | A worker employed through Deel PEO                                                |
| `contractor`   | An independent contractor                                                         |

### Contract type values

Pass any of these values in `contract_types`. They apply only alongside `audiences=client`:

| Value             | Product               |
| ----------------- | --------------------- |
| `eor`             | Employer of Record    |
| `gp`              | Global Payroll        |
| `ic`              | Contractor Management |
| `peo`             | PEO                   |
| `usp`             | US Payroll            |
| `deel_hr`         | Deel HR               |
| `deel_engage`     | Deel Engage           |
| `deel_it`         | Deel IT               |
| `deel_services`   | Deel Services         |
| `equity_services` | Equity Services       |
| `immigration`     | Immigration           |

This list grows as Deel adds products. Don't treat it as a fixed enum; tolerate unknown values instead of failing on them.

## Response structure

The response nests audiences, categories, and fields as follows:

```
data[]                        audience
 ├─ contract_types[]          present when value == "client"
 │   └─ categories[]
 └─ categories[]              every other audience
     ├─ fields[]              content to render
     └─ articles[]            metadata only
```

The `client` audience nests categories one level deeper than other audiences. Handle both shapes.

### Envelope

The top-level object carries the audience array alongside pagination metadata:

| Field         | Type      | Description                                   |
| ------------- | --------- | --------------------------------------------- |
| `data`        | `array`   | Audience objects                              |
| `has_more`    | `boolean` | Indicates whether more items are available    |
| `next_cursor` | `string`  | Cursor for the next page of results. Nullable |
| `total_count` | `number`  | Total number of audiences                     |

A guide is currently returned complete in a single response, so `has_more` is `false` and `next_cursor` is `null`. These fields exist to introduce pagination without a breaking change.

### Audience object

Each entry in `data` describes one audience:

| Field            | Type     | Required | Description                                                     |
| ---------------- | -------- | -------- | --------------------------------------------------------------- |
| `name`           | `string` | Yes      | Display name of the audience, for example `Contractor`          |
| `value`          | `string` | Yes      | Lowercase identifier for the audience, for example `contractor` |
| `categories`     | `array`  | No       | Categories applicable to this audience                          |
| `contract_types` | `array`  | No       | Contract types available for this audience                      |

### Contract type object

Each entry in `contract_types` describes one Deel product an employer uses:

| Field        | Type     | Required | Description                                                         |
| ------------ | -------- | -------- | ------------------------------------------------------------------- |
| `name`       | `string` | Yes      | Display name of the contract type, for example `Employer of Record` |
| `value`      | `string` | Yes      | Lowercase identifier for the contract type, for example `eor`       |
| `categories` | `array`  | Yes      | Categories applicable to this contract type                         |

### Category object

Each entry in `categories` groups the fields and articles for one topic:

| Field      | Type     | Required | Description                                                         |
| ---------- | -------- | -------- | ------------------------------------------------------------------- |
| `id`       | `string` | Yes      | Identifier for the category                                         |
| `name`     | `string` | Yes      | Display name of the category, for example `Wages and Working Hours` |
| `icon`     | `string` | Yes      | Icon aligning with the category                                     |
| `fields`   | `array`  | Yes      | Informative fields and their values                                 |
| `articles` | `array`  | Yes      | Articles connected to this category                                 |

### Field object

Each entry in `fields` carries one piece of renderable guide content:

| Field   | Type     | Required | Description                                        |
| ------- | -------- | -------- | -------------------------------------------------- |
| `id`    | `string` | Yes      | Identifier of the field                            |
| `name`  | `string` | Yes      | Title of the field, for example `EOR Minimum Wage` |
| `type`  | `string` | Yes      | One of `TEXT`, `LINK`, or `HTML`                   |
| `value` | `string` | No       | The value associated with the field. Nullable      |

### Article object

Metadata only; see [What the API does not provide](#what-the-api-does-not-provide).

| Field            | Type     | Required | Description                                               |
| ---------------- | -------- | -------- | --------------------------------------------------------- |
| `id`             | `string` | Yes      | Identifier for the article                                |
| `title`          | `string` | Yes      | Title of the article                                      |
| `description`    | `string` | Yes      | An excerpt for the article                                |
| `type`           | `string` | Yes      | One of `KNOWLEDGE` or `COMPLIANCE`                        |
| `featured_image` | `string` | Yes      | URI of the featured image                                 |
| `tags`           | `array`  | Yes      | Tag objects, each with `id`, `tag`, and `title`           |
| `created_at`     | `string` | Yes      | Timestamp when the article was created                    |
| `updated_at`     | `string` | Yes      | Timestamp when the article was last updated               |
| `effective_date` | `string` | No       | Date when the article content becomes effective. Nullable |

### Example response

A request filtered to the `eorEmployee` audience returns a payload of this shape:

```json
{
  "data": [
    {
      "name": "EOR Employee",
      "value": "eorEmployee",
      "categories": [
        {
          "id": "a3a9e1ce-7afb-40e2-a171-81ad2eec39ff",
          "name": "Wages and Working Hours",
          "icon": "MoreTime",
          "fields": [
            {
              "id": "b3a9e1ce-7afb-41d3-a171-81ad2eec39ff",
              "name": "EOR Minimum Wage",
              "type": "TEXT",
              "value": "ALL 40,000 per month"
            },
            {
              "id": "c4b8f2df-8bfc-42e4-b282-92be3ffd4a11",
              "name": "How deductions work",
              "type": "HTML",
              "value": "<p>Deductions are applied monthly.</p>"
            }
          ],
          "articles": []
        }
      ]
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "total_count": 1
}
```

### Conventions

Three conventions hold across the whole response:

* **Keys are `snake_case`** throughout the response.
* **Enumerated `type` values are uppercase**. Compare case-sensitively against uppercase values, or normalize on input.
* **Identifiers are stable UUIDs**. You can store them as foreign keys and rely on them across requests.

## Render the fields

Everything you display is a field. Branch on `type` and preserve the order in which fields arrive, because it reflects the order Deel intends for display.

> **Warning**
>
> The API reference defines the field object as `id`, `name`, `type`, and `value` only. Observed `LINK` fields additionally carry `label`, `url`, and `new_tab` in place of `value`. This shape is not yet reflected in the reference. Read `value` defensively and confirm the current shape with your Deel representative.

A renderer for the three current field types looks like this:

**`Python`**

```python Python
import html


def render_field(field):
    field_type = field["type"]

    if field_type == "TEXT":
        return {"label": field["name"], "body": html.escape(field.get("value") or "")}

    if field_type == "HTML":
        return {"label": field["name"], "body": sanitize(field.get("value") or "")}

    if field_type == "LINK":
        return {
            "label": field["name"],
            "href": field.get("url"),
            "text": field.get("label"),
            "target": "_blank" if field.get("new_tab") else "_self",
        }

    # Skip unrecognized types to stay forward compatible
    return None
```

**`Node.js`**

```javascript Node.js
function renderField(field) {
  switch (field.type) {
    case 'TEXT':
      return { label: field.name, body: escapeHtml(field.value ?? '') };

    case 'HTML':
      return { label: field.name, body: sanitize(field.value ?? '') };

    case 'LINK':
      return {
        label: field.name,
        href: field.url,
        text: field.label,
        target: field.new_tab ? '_blank' : '_self',
      };

    default:
      // Skip unrecognized types to stay forward compatible
      return null;
  }
}
```

Four rules govern correct rendering:

* **Sanitize `HTML` values before rendering them.** Pass the fragment through a sanitizer such as DOMPurify rather than assigning it directly to `dangerouslySetInnerHTML` or `v-html`.
* **Treat `value` as nullable.** The schema permits a null value on any field type.
* **Skip types you do not recognize.** New field types may be introduced. A default branch that returns nothing keeps your integration working when that happens.
* **Skip categories with no renderable fields.** A category qualifies for inclusion on either fields or articles, so `fields` can arrive empty.

### Handle empty categories

Filter out categories with no renderable fields before display; otherwise, they render as a heading with nothing beneath it.

**`Python`**

```python Python
def renderable_categories(categories):
    result = []
    for category in categories:
        fields = [f for f in (render_field(field) for field in category["fields"]) if f]
        if fields:
            result.append({**category, "fields": fields})
    return result


# The client audience nests categories one level deeper
def extract_categories(audience):
    if audience["value"] == "client":
        return [
            category
            for contract_type in audience.get("contract_types", [])
            for category in contract_type["categories"]
        ]
    return audience.get("categories", [])
```

**`Node.js`**

```javascript Node.js
const renderableCategories = (categories) =>
  categories
    .map((category) => ({
      ...category,
      fields: category.fields.map(renderField).filter(Boolean),
    }))
    .filter((category) => category.fields.length > 0);

// The client audience nests categories one level deeper
function extractCategories(audience) {
  if (audience.value === 'client') {
    return (audience.contract_types ?? []).flatMap((contractType) => contractType.categories);
  }
  return audience.categories ?? [];
}
```

## Error handling

The endpoint returns the following status codes.

| Status | Meaning                                                                 | Action                                                                       |
| ------ | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `400`  | The request is invalid, for example a missing or malformed country code | Supply a valid ISO 3166-1 alpha-2 code                                       |
| `401`  | The token is missing, expired, or unauthenticated                       | Re-authenticate and confirm the token carries the `knowledge-hub:read` scope |
| `403`  | The token is authenticated but lacks permission for this resource       | Confirm the required scope is granted to your application                    |
| `404`  | No country matches the supplied code                                    | Check the code. Not every country has a published guide                      |
| `500`  | The request failed on the Deel side                                     | Retry with exponential backoff. Contact Deel if the failure persists         |

Errors for `400` and `500` return an `errors` array whose entries carry `code`, `message`, and, for validation failures, a `field` naming the parameter at fault:

```json
{
  "errors": [
    {
      "code": "invalid_parameter",
      "field": "/data/country_code",
      "message": "Must have required property 'country_code'"
    }
  ]
}
```

Errors for `401`, `403`, and `404` return the shared Deel error envelope, which carries a `request` object alongside an `errors` array whose entries provide `message` and `path`:

```json
{
  "request": {
    "method": "GET",
    "url": "/rest/knowledge-hub/country-guide/BR",
    "status": 401,
    "api_req_id": "00000000-0000-0000-0000-000000000000"
  },
  "errors": [
    {
      "message": "Unauthorized"
    }
  ]
}
```

> **Note**
>
> Where a machine-readable `code` is present, branch on it rather than on `message`. Message wording is subject to change.

Empty responses

A `200` response carrying `"data": []` is valid. The country exists, but no guide is published for the audiences you requested. Treat it as an empty state.

The same applies per audience. A request for three audiences may return one if only one is published.

> **Warning**
>
> Do not assume `data` mirrors your request, and do not index into it positionally. Match entries on `value`.

## Best practices

The following practices keep an integration stable as guide content and the API change:

#### Caching and refresh

* **No webhooks are available** for this content, and there is no way to request only what changed. Each refresh is a full fetch for a country.
* **Guides change on the order of weeks**, not hours. A daily refresh is sufficient for most integrations. Hourly polling provides no benefit.
* **Cache the full response per country** and serve your users from your cache.

#### Rate limits and versioning

* Respect `429` responses with exponential backoff. See [Rate limits](/api/rate-limits).
* Pin requests with the `X-Version` header and follow the Deel API changelog for changes.

#### Configuration

Two things are not discoverable at runtime, so plan to configure them:

* **Which countries have published guides.** Request the current list from your Deel representative instead of discovering it by probing codes and collecting 404 responses.
* **Which audiences and contract types apply to your integration.** Use the reference tables above and confirm the relevant values with your Deel representative.

## Next steps

Continue with the endpoint schema and the related guides:

#### [Knowledge Hub reference](/api/reference/endpoints/knowledge-hub)

Review the full endpoint schema in the API reference

#### [Getting started](/api/partners/getting-started)

Build your first OAuth 2.0 app with Deel

#### [Authentication](/api/authentication)

Learn how to authenticate API requests

#### [API versioning](/api/api-versioning)

Pin your integration to a specific API version