# Third-party API integration

**Summary:** How a third-party application authenticates with an M2M token and calls the Subrite API to find, list, create and update members, create subscriptions, and read packages, podcasts and assets.

- Space: [Developers](https://www.subrite.no/developers)
- Source: https://www.subrite.no/developers/third-party-api-integration
- Updated: 2026-09-23
- Markdown index: https://www.subrite.no/developers/llms.txt

<a id="overview"></a>
## Overview

This guide helps third-party applications integrate with the Subrite API and use the correct API calls to meet business needs. It gives examples of key business operations you can perform with the Subrite API.

<a id="scope-of-the-integration"></a>
## Scope of the integration

Third-party applications that integrate with the Subrite API should keep existing functionality working while ensuring smooth operations. The tasks to perform are defined by business requirements and may include:

1. Fetching member information
2. Checking if an email exists before creating a member
3. Updating an existing member with a new membership
4. Creating a new member with a new membership
5. Updating custom properties

The guide is meant to help any third-party service provider integrate with the Subrite API efficiently, with a seamless user experience and consistent data.

<a id="prerequisites"></a>
## Prerequisites

Before you integrate with the Subrite API, make sure you meet the following requirements.

<a id="obtain-an-m2m-access-token"></a>
### Obtain an M2M access token

The Subrite API requires an **M2M (machine-to-machine) access token** for authentication. Generate the token in the environment you are integrating with:

| Environment | Frontend | API |
| --- | --- | --- |
| Stage | `*.subrite.no` (contact Subrite to get access) | `stage.api.subrite.no` |
| Production | `minside.*.no` (contact Subrite to get access) | `api.subrite.no` |

Create an M2M token that grants Member, Subscription and Content permissions. You receive the token when you click **Save**.

![The New M2M Client Setting dialog with Read and Update selected for the Member and Subscription scopes](https://cdn.sanity.io/images/1x2xswq6/production/fbd57e2865d820d75da67bb3243ba6e6f651b27e-765x650.png?w=1600&fit=max&auto=format)

*Create an M2M token*

<a id="api-details"></a>
## API details

This section describes the API operations available for third-party integrations.

<a id="filter-members"></a>
### Filter members

To find an existing member by **email** or **custom properties**, use the following API call:

<a id="endpoint"></a>
#### Endpoint

```text
POST /api/v1/members/filter
```

<a id="headers"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body"></a>
#### Body

```json
{
  "filters": [
    {
      "field": "updatedAt",
      "operator": "gte",
      "value": "2024-01-08T07:02:23.742Z"
    },
    {
      "field": "email",
      "operator": "eq",
      "value": "example@email.no"
    },
    {
      "field": "customProperty.propertyKey",
      "operator": "eq",
      "value": 1144
    },
    {
      "field": "subscriptionPlanId",
      "operator": "eq",
      "value": 17
    },
    {
      "field": "subscriptionStatus",
      "operator": "eq",
      "value": "active"
    }
  ]
}
```

> [!NOTE]
> **Supported filter fields**
>
> The request body key is `filters`, and the subscription status field is `subscriptionStatus`. Earlier revisions of this page showed `filter` and `subscriptionPlanStatus`; neither name is accepted.
>
> This endpoint applies exactly these fields: `updatedAt`, `id`, `email`, `customProperty.<name>`, `subscriptionStatus` and `subscriptionPlanId`. Anything else is rejected rather than ignored. An unrecognized name returns `errors: [{ field: "field", messageCode: "invalidFormat" }]`, and a recognized name outside the list above returns `messageCode: "memberFilterFieldUnsupported"`.
>
> A custom property filter **must** carry the property name in the field, as `customProperty.propertyKey`. The bare `customProperty` field is rejected here: without the name there is nothing to compare, and it previously returned an empty page rather than an error.
>
> The two member filter endpoints accept overlapping but different field sets, and neither is a superset of the other. For member attributes this endpoint does not list (`name`, `phone`, `postCode`, `consent`, `inactivity`, `emailStatus`, `search`, `type`, `isCompany`), use `POST /api/v1/members/list`. For `subscriptionStatus`, `subscriptionPlanId` and the dotted custom property form, use this one.
>
> **If you are upgrading:** as with the list endpoint, the only calls that change are ones already returning the wrong rows. An ignored filter meant you got the whole tenant back with a `200`, and a bare `customProperty` filter meant you got nothing back. Check your payloads against the six fields above; if they all appear there, nothing changes. There is no data migration and nothing to roll back.

<a id="response"></a>
#### Response

```javascript
{
  "items": [
    {
      "id": "string", // Updated: memberId is now a string,Ex: "1", "c29a724f-36cf-4584-9d47-1cdde8733f75" etc
      "fullName": "string",
      "nickName": "string",
      "email": "string",
      "birthDate": "2024-01-08T07:02:23.742Z",
      "phone": "+4793155388", // Updated: Remove countryCode Return Phone along with calling code
      "isCompany": true,
      "companyName": "string",
      "organizationNo": "string",
      "memberNumber": 1234, // memberNumber is a number
      "customPropertyValues": {
        "propertyKey": "propertyValue"
      }
    }
  ]
}
```

For all details, see [Get all members of the tenant with filter and pagination](https://docs.subrite.no/api-reference#tag/member/post/api/v1/members/filter) in the API reference.

<a id="list-members"></a>
### List members

To list the members of the tenant with minimal data and pagination, use an **M2M token** with the **MEMBER READ** permission and the following API call:

<a id="endpoint-2"></a>
#### Endpoint

```text
POST /api/v1/members/list?page=1&take=20
```

<a id="headers-2"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-2"></a>
#### Body

The body is optional and can be used to filter the list.

```json
{
  "filters": []
}
```

<a id="filter-validation"></a>
#### Filter validation

Every filter you send must be one this list can apply. All three rejections below return `400`, but they carry different codes, so branch on the code rather than the status.

| What you sent | Response |
| --- | --- |
| A `field` that is not a recognized filter name at all (for example `subscriptionPlanStatus`) | `errors: [{ field: "field", messageCode: "invalidFormat" }]` |
| A recognized `field` this endpoint cannot serve: `subscriptionStatus`, `subscriptionPlanId` or the dotted `customProperty.<name>` form. Use `POST /api/v1/members/filter` for those. Note that this endpoint takes the bare `customProperty` field with the name in `value`. | `messageCode: "memberFilterFieldUnsupported"` |
| A recognized `field` whose `value` shape the list cannot compare (for example a `consent` filter missing `status`) | `messageCode: "memberFilterValueInvalid"` |

Previously all three were ignored, which meant a request that asked for a narrow slice could come back with every member in the tenant and a matching total count. Send an empty `filters` array (or no body) when you want the whole list.

> [!NOTE]
> **If you are upgrading**
>
> The only calls that change behaviour are ones that were already returning the wrong rows. A request whose filters were all being ignored used to return `200` with the entire tenant; it now returns `400`. Audit your filter payloads against the table above. If every field you send is allowed there, nothing changes for you.
>
> There is no data migration and nothing to roll back on your side. If a `400` blocks you, drop the unsupported filter and narrow the result set in your own code until you can move the query to `POST /api/v1/members/filter`.

<a id="response-2"></a>
#### Response

```json
{
  "items": [
    {
      "memberId": "1",
      "memberNumber": 1234,
      "fullName": "string",
      "isCompany": false,
      "companyName": "string",
      "email": "string",
      "phone": "+4793155388",
      "createdAt": "2024-01-08T07:02:23.742Z",
      "memberEmailStatus": "delivered",
      "totalRevenue": 0,
      "hasActiveSubscription": true
    }
  ],
  "meta": {
    "totalItems": 1,
    "itemCount": 1,
    "itemsPerPage": 20,
    "totalPages": 1,
    "currentPage": 1
  }
}
```

For all details, see [Get all members of the tenant with minimal data with pagination](https://docs.subrite.no/api-reference#tag/member/post/api/v1/members/list) in the API reference.

<a id="list-member-subscriptions"></a>
### List member subscriptions

To list the subscriptions of the tenant with pagination, use an **M2M token** with the **SUBSCRIPTION READ** permission and the following API call:

<a id="endpoint-3"></a>
#### Endpoint

```text
POST /api/v1/members/subscriptions/list?page=1&take=20
```

<a id="headers-3"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-3"></a>
#### Body

The body is optional and can be used to filter the list.

```json
{
  "filters": []
}
```

<a id="response-3"></a>
#### Response

```json
{
  "items": [
    {
      "id": 1,
      "payer": "string",
      "memberNumber": 1234,
      "subscriptionPlan": "string",
      "lastPaidAmount": 0,
      "recurringPrice": 0,
      "autoRenew": true,
      "hasCustomPrice": false,
      "activeSubscriptionDays": 0,
      "nextRenewal": "2024-01-08T07:02:23.742Z",
      "paymentProvider": "stripe",
      "status": "active",
      "scheduledActivationDate": null
    }
  ],
  "meta": {
    "totalItems": 1,
    "itemCount": 1,
    "itemsPerPage": 20,
    "totalPages": 1,
    "currentPage": 1
  }
}
```

For all details, see [Get all subscriptions of the tenant with pagination](https://docs.subrite.no/api-reference#tag/member/post/api/v1/members/subscriptions/list) in the API reference.

<a id="create-or-update-a-member"></a>
### Create or update a member

When you integrate with the Subrite API, you must first **check if a member exists** before you decide whether to create a new member or update an existing one.

> [!WARNING]
> **Important**
>
> - If you try to **create a member** with an existing `email`, `customPropertyKey1` or `customPropertyKey2`, the API **rejects the request**.
> - To avoid conflicts, **always check if the member exists first** before you send a create request.

<a id="step-1-check-if-the-member-exists"></a>
#### Step 1: Check if the member exists

Use the filter members API (described in the previous section) to check if a member exists.

- If the **member exists**, the response includes the member ID in the `id` field.
- Use this ID as `memberId` to **update the member** instead of creating a new one.

The next sections describe how to create and update members.

<a id="create-a-member"></a>
### Create a member

To create a new member in Subrite, use the following API request.

<a id="endpoint-4"></a>
#### Endpoint

```text
POST /api/v1/members
```

<a id="headers-4"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-4"></a>
#### Body

```javascript
{
  "fullName": "string",
  "email": "string",
  "phone": "+4712345678", // Updated: Remove countryCode, Send calling code along with phone
  "birthDate": "2024-01-08T11:26:36.376Z",
  "nickName": "string",
  "isCompany": true,
  "companyName": "string",
  "address": {
    "line1": "string",
    "line2": "string",
    "city": "string",
    "state": "string",
    "postCode": "string",
    "country": "string",
    "addressType": "primary"
  },
  "customPropertyValues": {}
}
```

<a id="response-4"></a>
#### Response

```javascript
{
  "id": "string", // Updated: memberId is now a string,Ex: "1", "c29a724f-36cf-4584-9d47-1cdde8733f75" etc
  "fullName": "string",
  "email": "string",
  "phone": "+4793155388", // Updated: Remove countryCode Return Phone along with calling code
  "birthDate": "2024-01-08T11:26:36.376Z",
  "nickName": "string",
  "isCompany": true,
  "companyName": "string",
  "memberNumber": 1234, // memberNumber is a number
  "address": {
    "line1": "string",
    "line2": "string",
    "city": "string",
    "state": "string",
    "postCode": "string",
    "country": "string",
    "addressType": "primary"
  },
  "customPropertyValues": {
    "propertyKey": "propertyValue"
  }
}
```

For all details, see [Create a new member](https://docs.subrite.no/api-reference#tag/member/post/api/v1/members) in the API reference.

<a id="update-a-member"></a>
### Update a member

To update an existing member, use the following API request.

<a id="endpoint-5"></a>
#### Endpoint

```text
PATCH /api/v1/members/{memberId}
```

<a id="headers-5"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-5"></a>
#### Body

```javascript
{
  "fullName": "string",
  "email": "string",
  "phone": "+4712345678", // Updated: Remove countryCode, Send calling code along with phone
  "birthDate": "2024-01-08T11:26:36.376Z",
  "nickName": "string",
  "isCompany": true,
  "companyName": "string",
  "address": {
    "line1": "string",
    "line2": "string",
    "city": "string",
    "state": "string",
    "postCode": "string",
    "country": "string",
    "addressType": "primary"
  },
  "customPropertyValues": {
    "propertyKey": "propertyValue"
  }
}
```

<a id="response-5"></a>
#### Response

```javascript
{
  "id": "string", // Updated: memberId is now a string,Ex: "1", "c29a724f-36cf-4584-9d47-1cdde8733f75" etc
  "fullName": "string",
  "email": "string",
  "phone": "+4793155388", // Updated: Remove countryCode Return Phone along with calling code
  "birthDate": "2024-01-08T11:26:36.376Z",
  "nickName": "string",
  "isCompany": true,
  "companyName": "string",
  "organizationNo": "string",
  "memberNumber": 1234, // memberNumber is a number
  "address": {
    "line1": "string",
    "line2": "string",
    "city": "string",
    "state": "string",
    "postCode": "string",
    "country": "string",
    "addressType": "primary"
  },
  "customPropertyValues": {
    "propertyKey": "propertyValue"
  }
}
```

For all details, see [Update a member](https://docs.subrite.no/api-reference#tag/member/patch/api/v1/members/{id}) in the API reference.

<a id="update-a-member-s-custom-properties"></a>
### Update a member's custom properties

To update a member's custom properties, use the following API request.

<a id="endpoint-6"></a>
#### Endpoint

```text
PATCH /api/v1/members/{memberId}
```

<a id="headers-6"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-6"></a>
#### Body

```json
{
  "customPropertyValues": {
    "propertyKey": "propertyValue"
  }
}
```

For all details, see [Update a member](https://docs.subrite.no/api-reference#tag/member/patch/api/v1/members/{id}) in the API reference.

<a id="create-a-subscription"></a>
### Create a subscription

To subscribe a member to a product package, use the following API request.

<a id="endpoint-7"></a>
#### Endpoint

```text
POST /api/v1/product-packages/subscribe
```

<a id="headers-7"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="body-7"></a>
#### Body

```json
{
  "packageId": 17,
  "optionalProducts": [
    {
      "productId": 201,
      "quantity": 2,
      "shippingCountry": "NO"
    },
    {
      "productId": 202,
      "quantity": 1
    }
  ],
  "paymentDate": "2024-01-08T07:43:30.122Z",
  "memberId": "1",
  "reference": ""
}
```

<a id="field-descriptions"></a>
#### Field descriptions

- `packageId` (number, required): the ID of the product package.
- `optionalProducts` (array, optional): list of optional products with quantity and an optional shipping country.
  - `productId` (number, required): the ID of the product.
  - `quantity` (number, required, min: 1): number of units of this product.
  - `shippingCountry` (string, optional): country code, if the product requires shipping.
- `paymentDate` (ISO 8601 date string, optional): the date of payment (for example `"2024-01-08T07:43:30.122Z"`).
- `memberId` (string, required): the ID of the member. Examples: `"1"`, `"c29a724f-36cf-4584-9d47-1cdde8733f75"`.
- `reference` (string, optional): a custom reference string for the subscription.

> [!NOTE]
> **Payment is treated as collected**
>
> When you use this API, Subrite treats the payment as already collected. The payment method is updated to **E-invoice**, and `nextOrderDate` is set so that the next renewal payment is processed via E-invoice.

> [!CAUTION]
> **Enable E-invoice first**
>
> Make sure the **E-invoice payment method** is enabled for the tenant before you use this API.

For all details, see [Create a subscription for a pre-purchased package](https://docs.subrite.no/api-reference#tag/product-package/post/api/v1/product-packages/subscribe) in the API reference.

<a id="get-packages-by-filters"></a>
### Get packages by filters

To find packages with filters, use an **M2M token** with the **PLANS\_AND\_PRODUCTS READ** permission and the following API call:

<a id="endpoint-8"></a>
#### Endpoint

```text
GET /api/v1/product-packages/all
```

<a id="headers-8"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="query-parameters-filters"></a>
#### Query parameters (filters)

All filters are optional. Combine several filters to narrow down the results.

- `status` (string or array, optional): filter packages by status. Accepts one or more values. Available values: `draft`, `published`, `unpublished`. When not provided, packages with all statuses are returned.
- `isAvailableViaSlug` (boolean, optional): filter packages that are available via slug. When `true`, returns only packages that can be accessed through a slug URL.
- `isAvailableOnMyPage` (boolean, optional): filter packages that are available on the member's "My Page". When `true`, returns only packages that members can see and manage from their personal page.
- `isAvailableForManualOrderViaAdmin` (boolean, optional): filter packages that are available for manual order by an admin. When `true`, returns only packages that administrators can order manually on behalf of members.
- `enableGiftSubscription` (boolean, optional): filter packages that have gift subscription enabled. When `true`, returns only packages where the associated subscription plan has gift subscription enabled.

<a id="example-requests"></a>
#### Example requests

```text
# Filter by single status
GET /api/v1/product-packages/all?status=published

# Filter by multiple statuses
GET /api/v1/product-packages/all?status=published&status=draft

# Combine with other filters
GET /api/v1/product-packages/all?status=published&isAvailableViaSlug=true&isAvailableOnMyPage=true
```

<a id="response-6"></a>
#### Response

```json
[
  {
    "id": 17,
    "salesTitle": "Premium Package",
    "name": "Premium Package",
    "slug": "premium-package",
    "image": "https://example.com/image.jpg",
    "description": ["Package description"],
    "price": 299.0,
    "recurringPrice": 299.0,
    "status": "publish",
    "isAvailableViaSlug": true,
    "isAvailableOnMyPage": true,
    "isAvailableForManualOrderViaAdmin": false,
    "products": [
      {
        "productId": 201,
        "name": "Product Name",
        "price": 299.0,
        "recurringPrice": 299.0,
        "currency": "NOK",
        "tax": 25,
        "isOptional": false,
        "productCategory": "digital_subscription"
      }
    ],
    "subscriptionCount": 150
  }
]
```

For all details, see [Get packages by filters](https://docs.subrite.no/api-reference#tag/product-package/get/api/v1/product-packages/all) in the API reference.

<a id="get-package-details-by-id"></a>
### Get package details by ID

To get the details of a specific package by ID, use an **M2M token** with the **PLANS\_AND\_PRODUCTS READ** permission and the following API call:

<a id="endpoint-9"></a>
#### Endpoint

```text
GET /api/v1/product-packages/details/:id
```

<a id="headers-9"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="path-parameters"></a>
#### Path parameters

- `id` (number, required): the ID of the package.

<a id="example-request"></a>
#### Example request

```text
GET /api/v1/product-packages/details/17
```

<a id="response-7"></a>
#### Response

```json
{
  "id": 17,
  "salesTitle": "Premium Package",
  "name": "Premium Package",
  "slug": "premium-package",
  "image": "https://example.com/image.jpg",
  "description": ["Package description"],
  "price": 299.0,
  "recurringPrice": 299.0,
  "status": "publish",
  "isAvailableViaSlug": true,
  "isAvailableOnMyPage": true,
  "isAvailableForManualOrderViaAdmin": false,
  "billingPeriod": "monthly",
  "products": [
    {
      "productId": 201,
      "name": "Product Name",
      "price": 299.0,
      "recurringPrice": 299.0,
      "basePrice": 299.0,
      "currency": "NOK",
      "tax": 25,
      "isOptional": false,
      "productCategory": "digital_subscription",
      "properties": null,
      "recurringProperties": null,
      "description": "Product description",
      "shippingCharges": []
    }
  ],
  "subscriptionCount": 150,
  "paymentProviders": ["stripe", "vipps"],
  "customInputFields": {
    "memberType": true,
    "invoiceAddress": true,
    "city": true
  }
}
```

For all details, see [Get package details by ID](https://docs.subrite.no/api-reference#tag/product-package/get/api/v1/product-packages/details/{id}) in the API reference.

<a id="get-all-available-podcasts"></a>
### Get all available podcasts

To get all configured podcasts, use an **M2M token** with the **CONTENT READ** permission and the following API call:

<a id="endpoint-10"></a>
#### Endpoint

```text
GET /api/v1/podcast-admin
```

<a id="headers-10"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="response-8"></a>
#### Response

```json
[
  {
    "id": 1,
    "createdAt": "2024-12-03T04:03:05.156Z",
    "updatedAt": "2025-10-22T04:57:01.219Z",
    "deletedAt": null,
    "metaData": null,
    "name": "Test Name",
    "rssFeedUrl": "",
    "imageUrl": "",
    "showUrl": "",
    "description": "Test description",
    "sortOrder": 0,
    "tenantId": 0
  }
]
```

For all details, see [Get all podcasts of the tenant](https://docs.subrite.no/api-reference#tag/podcast-admin/get/api/v1/podcast-admin) in the API reference.

<a id="get-podcast-details"></a>
### Get podcast details

To get the details of a specific podcast by ID, use an **M2M token** with the **CONTENT READ** permission and the following API call:

<a id="endpoint-11"></a>
#### Endpoint

```text
GET /api/v1/podcast-admin/:id
```

<a id="headers-11"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="response-9"></a>
#### Response

```json
{
  "id": 1,
  "createdAt": "2024-12-03T04:03:05.156Z",
  "updatedAt": "2025-10-22T04:57:01.219Z",
  "deletedAt": null,
  "metaData": null,
  "name": "Test Name",
  "rssFeedUrl": "",
  "imageUrl": "",
  "showUrl": "",
  "description": "Test description",
  "sortOrder": 0,
  "tenantId": 0
}
```

For all details, see [Get podcast details by ID](https://docs.subrite.no/api-reference#tag/podcast-admin/get/api/v1/podcast-admin/{id}) in the API reference.

<a id="list-asset-folders"></a>
### List asset folders

To list the tenant's asset folders with per-folder file counts and tenant-wide usage totals, use an **M2M token** with the **CONTENT READ** permission and the following API call. Assets are files (images, PDFs, documents and so on) uploaded to the tenant's media library and served publicly from the tenant's dedicated CDN domain.

<a id="endpoint-12"></a>
#### Endpoint

```text
GET /api/v1/asset-admin/folders
```

<a id="headers-12"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="response-10"></a>
#### Response

```json
{
  "folders": [
    {
      "id": 1,
      "name": "Brand",
      "slug": "brand",
      "fileCount": 6,
      "createdAt": "2026-02-02T09:15:00.000Z"
    }
  ],
  "totalFileCount": 24,
  "totalSizeBytes": 18452312,
  "cdnDomain": "assets.subrite.no"
}
```

- `folders`: the tenant's folders. `slug` is the first path segment of every file's public URL in that folder.
- `totalFileCount` and `totalSizeBytes`: totals across the whole tenant, including root-level files (files not in any folder).
- `cdnDomain`: the domain files are served from. The public URL of a file is `https://{cdnDomain}/{tenantId}/{folderSlug}/{fileName}` (root-level files omit the `{folderSlug}` segment).

For all details, see [List folders with counts and usage totals](https://docs.subrite.no/api-reference#tag/asset-admin/get/api/v1/asset-admin/folders) in the API reference.

<a id="list-assets"></a>
### List assets

To list, search and sort the tenant's assets with pagination, use an **M2M token** with the **CONTENT READ** permission and the following API call. Search and sort span all of the tenant's assets.

<a id="endpoint-13"></a>
#### Endpoint

```text
GET /api/v1/asset-admin/assets?page=1&take=24
```

<a id="headers-13"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="query-parameters"></a>
#### Query parameters

All parameters are optional.

- `page` (number, default: 1): page number.
- `take` (number, 1 to 50, default: 24): page size.
- `folderId` (number, optional): restrict to one folder. Omit it for all files; pass `0` or `root` for root-level files (files not in any folder).
- `kinds` (string, optional): restrict to certain file kinds, for example `kinds=image,svg` (or a repeated `kinds` parameter). Any of `image`, `svg`, `pdf`, `video`, `audio`, `font`, `doc`.
- `search` (string, optional): case-insensitive match on the display name.
- `sort` (string, optional): one of `name`, `size`, `uploaded` (default: `uploaded`).
- `order` (string, optional): `asc` or `desc` (default: `desc`).

<a id="response-11"></a>
#### Response

```json
{
  "items": [
    {
      "id": 42,
      "folderId": 1,
      "title": "logo-subrite-primary.svg",
      "name": "logo-subrite-primary.svg",
      "url": "https://assets.subrite.no/17/brand/logo-subrite-primary.svg",
      "kind": "svg",
      "mimeType": "image/svg+xml",
      "sizeBytes": 8192,
      "width": 512,
      "height": 512,
      "altText": null,
      "createdAt": "2026-02-02T09:15:00.000Z"
    }
  ],
  "meta": {
    "totalItems": 24,
    "itemCount": 24,
    "itemsPerPage": 24,
    "totalPages": 1,
    "currentPage": 1
  }
}
```

- `folderId`: `null` for root-level files.
- `title`: the human-readable display name. `name`: the URL-safe file name (the last segment of `url`).
- `url`: the public CDN URL to link to or embed directly.
- `kind`: one of `image`, `svg`, `pdf`, `video`, `audio`, `font`, `doc`.
- `width` and `height`: present for images and SVGs, otherwise `null`.

For all details, see [List/search/sort assets](https://docs.subrite.no/api-reference#tag/asset-admin/get/api/v1/asset-admin/assets) in the API reference.

<a id="get-an-asset"></a>
### Get an asset

To fetch a single asset by ID, use an **M2M token** with the **CONTENT READ** permission and the following API call:

<a id="endpoint-14"></a>
#### Endpoint

```text
GET /api/v1/asset-admin/assets/:id
```

<a id="headers-14"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
Content-Type: application/json
```

<a id="path-parameters-2"></a>
#### Path parameters

- `id` (number, required): the ID of the asset.

<a id="response-12"></a>
#### Response

```json
{
  "id": 42,
  "folderId": 1,
  "title": "logo-subrite-primary.svg",
  "name": "logo-subrite-primary.svg",
  "url": "https://assets.subrite.no/17/brand/logo-subrite-primary.svg",
  "kind": "svg",
  "mimeType": "image/svg+xml",
  "sizeBytes": 8192,
  "width": 512,
  "height": 512,
  "altText": null,
  "createdAt": "2026-02-02T09:15:00.000Z"
}
```

For all details, see [Get a single asset](https://docs.subrite.no/api-reference#tag/asset-admin/get/api/v1/asset-admin/assets/{id}) in the API reference.

<a id="download-an-asset"></a>
### Download an asset

To download an asset's file bytes, use an **M2M token** with the **CONTENT READ** permission and the following API call. Unlike the public `url` (which the CDN serves inline), this endpoint streams the object with a `Content-Disposition: attachment` header, so browsers and HTTP clients save it as a file using its display name.

<a id="endpoint-15"></a>
#### Endpoint

```text
GET /api/v1/asset-admin/assets/:id/download
```

<a id="headers-15"></a>
#### Headers

```text
Authorization: Bearer {m2m_token}
```

<a id="path-parameters-3"></a>
#### Path parameters

- `id` (number, required): the ID of the asset.

<a id="response-13"></a>
#### Response

The raw file bytes, with these headers:

- `Content-Type`: the asset's MIME type (for example `image/svg+xml` or `application/pdf`).
- `Content-Disposition`: `attachment; filename="..."` using the asset's display name.

For all details, see [Download an asset](https://docs.subrite.no/api-reference#tag/asset-admin/get/api/v1/asset-admin/assets/{id}/download) in the API reference.

<a id="summary-of-key-api-calls"></a>
## Summary of key API calls

| API operation | API endpoint |
| --- | --- |
| Fetch member info | `POST /api/v1/members/filter` |
| Check if email exists | `POST /api/v1/members/filter` |
| List members | `POST /api/v1/members/list` |
| List member subscriptions | `POST /api/v1/members/subscriptions/list` |
| Update existing member | `PATCH /api/v1/members/{memberId}` |
| Create new member | `POST /api/v1/members` |
| Create subscription | `POST /api/v1/product-packages/subscribe` |
| Update custom properties | `PATCH /api/v1/members/{memberId}` |
| Fetch packages by filters | `GET /api/v1/product-packages/all` |
| Fetch package details | `GET /api/v1/product-packages/details/{id}` |
| Fetch all podcasts | `GET /api/v1/podcast-admin` |
| Fetch podcast details | `GET /api/v1/podcast-admin/{id}` |
| List asset folders | `GET /api/v1/asset-admin/folders` |
| List assets | `GET /api/v1/asset-admin/assets` |
| Fetch an asset | `GET /api/v1/asset-admin/assets/{id}` |
| Download an asset | `GET /api/v1/asset-admin/assets/{id}/download` |

<a id="changelog"></a>
## Changelog

<a id="filter-members-api"></a>
### Filter members API

- API call: `POST /api/v1/members/filter`
- Response update:

```javascript
"phone": "+4793155388" // Removed countryCode, returning phone along with calling code
```

<a id="create-member-api"></a>
### Create member API

- API call: `POST /api/v1/members`
- Response update:

```javascript
"phone": "+4793155388" // Removed countryCode, returning phone along with calling code
```

<a id="update-member-api"></a>
### Update member API

- API call: `PATCH /api/v1/members/{memberId}`
- Response update:

```javascript
"phone": "+4793155388" // Removed countryCode, returning phone along with calling code
```

<a id="memberid-type-change"></a>
### memberId type change

- API call: `PATCH /api/v1/members/{memberId}`
- Response update:

```javascript
"memberId": "1" // Updated: memberId is now a string,Ex: "1", "c29a724f-36cf-4584-9d47-1cdde8733f75" etc
```
