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

# Bulk upsert partner entries

POST https://v1.api.partnerpage.io/api/directory/private/v1alpha/external/directories/{directoryId}/entries/partners/bulk/
Content-Type: application/json

Create or update partner entries in a directory in a single batch. Each row is identified by exactly one of `entry_id` or `external_id`; rows identified by `external_id` are upserted when no matching entry exists. Batch size is capped by the configured maximum. The response contains a per-entry result with `action` (`created`, `updated`, `unchanged`, or `failed`). Successful rows include the resulting `entry`. Failed rows include a top-level `message` for authorization or not-found failures, and `errors` when the row has structured field validation failures. A row with `message` equal to `Profile is being updated; retry this row` can be resent after a concurrent profile update finishes.

Fields a row does not send are left unchanged.

Directory-owner rows publish the submitted fields even while a partner has ongoing changes or a draft in review. Submitted values are authoritative, including values already live: they are copied into the draft without changing its approval state. Pending fields and sections omitted from the row are preserved.

* `tier`: ID of one of the directory's tiers. `null` puts the entry back on the directory's default tier. Directory owners only; the row fails when tiers are not enabled or the tier belongs to another directory.
* `featured`, `displayOnInternalDirectory`, `internalDescription` and the `internal...Cta...` fields: directory owners only; the row fails when the matching feature (featured entries, internal directory) is not enabled.
* `userAssignedFilterOptions`: replaces the entry's options in every filter with the sent list.
* `userAssignedFilterOptionsByFilter`: list of `{filterId, optionIds}`; replaces the options of the listed filters only and keeps every other filter as it is. An empty `optionIds` clears that filter. Cannot be combined with `userAssignedFilterOptions` in the same row. Option rules (selection limits, required filters, approval flow) are checked on the resulting full list and reported under `userAssignedFilterOptions`.
* `certifications`: list of `{certificationId, quantity}` using the directory's certifications; replaces the entry's certifications with exactly this list, in this order. `quantity` is optional and defaults to 1. An empty list removes every certification. A partner holds at most 10, and a certification can be listed once. The row fails when certifications are not enabled or a certification belongs to another directory.
* `badges`: list of `{id}` using the directory's badges; replaces the entry's badges with exactly this list, in this order (an `order` sent with a badge is ignored). An empty list removes every badge; `null` keeps them. A partner holds at most 10, and a badge can be listed once. The row fails when badges are not enabled and the list is not empty, or a badge belongs to another directory. When the directory manages badges, an entry owner's row with a changed list fails with `errors.badges`, or with `message` if the badges change between that check and the write; an unchanged list is accepted. Each successful row's `entry` lists the badges in badge order.

A row with a field this endpoint does not accept (for example `emails`: bulk rows never send owner invitations) fails with an error per unknown field. Fields returned by the partner read response are accepted, and read-only ones (`id`, `type`, `slug`, `lastModified`, `awards`, `systemAssignedFilterOptions`, ...) are ignored, so a row copied from a read can be sent back.

Reference: https://docs.partnerpage.io/api-reference/entry/partner-entry/bulk-upsert-partner-entries

## Authentication

- `Authorization` header (required) (prefixed with ` Token   `) — Your API key, sent as `Authorization: Token <your API key>`

## Request

### Path parameters

- `directoryId` (string, required) — ID of the directory containing the partner entries

### Body (application/json)

This endpoint expects a BulkPartnerEntryUpdateRequestRequest.

- `entries` (list of PatchedBulkPartnerEntryUpdateItemRequest, required)

## Response

### 200

Per-entry results

- `list of BulkPartnerEntryUpdateResultItem`

## Errors

### 400 Bad Request Error

Invalid payload or batch size exceeds maximum

- `any`

### 404 Not Found Error

Directory not found

- `any`

## Types

### PatchedBulkPartnerEntryUpdateItemRequest

Base Entry Serializer. Fields common to read and write operations of all entry types.

- `title` (string, optional)
- `headline` (string, optional, nullable)
- `customSlug` (string, optional, nullable)
- `status` (enum, optional) — * `hidden` - hidden * `inactive` - inactive * `public` - public * `searchable` - searchable
  - Allowed values: `hidden`, `inactive`, `public`, `searchable`
- `icon` (string, optional, nullable)
- `image` (string, optional)
- `iconDark` (string, optional, nullable)
- `imageDark` (string, optional, nullable)
- `website` (string, optional, nullable)
- `description` (string, optional)
- `integrationDescription` (string, optional, nullable)
- `iconUrl` (string, optional, nullable)
- `imageUrl` (string, optional, nullable)
- `location` (string, optional, nullable)
- `userAssignedFilterOptions` (list of string, optional)
- `externalUrl` (string, optional, nullable)
- `callToActionUrl` (string, optional, nullable) — An http(s) address or a mailto: link.
- `callToActionLabel` (string, optional, nullable)
- `supportEmail` (string, optional, nullable)
- `developerName` (string, optional, nullable)
- `availability` (boolean, optional)
- `embedCode` (string, optional, nullable) — The embed code as the provider gives it. Only the first iframe with an https address is kept; null or blank removes the embed
- `embedTitle` (string, optional, nullable)
- `embedHeight` (integer, optional, nullable)
- `profileFields` (map from string to any, optional) — Values of the directory's profile fields by field key. Keys sent are set, null (or blank text, or \[] for a multiple choice) clears one, keys not sent are kept. An image or file field takes the URL that POST /api/core/private/v1alpha/files/ returned, or \{url, name}
- `badges` (list of ExternalPartnerEntryBadgeRequest, optional, nullable)
- `entryId` (string, optional, nullable)
- `externalId` (string, optional, nullable)
- `tier` (string, optional, nullable)
- `featured` (boolean, optional)
- `displayOnInternalDirectory` (boolean, optional)
- `internalDescription` (string, optional, nullable)
- `internalPointOfContactCtaUrl` (PatchedBulkPartnerEntryUpdateItemRequestInternalPointOfContactCtaUrl, optional, nullable)
- `internalPointOfContactCtaLabel` (string, optional, nullable)
- `internalChannelContactCtaUrl` (PatchedBulkPartnerEntryUpdateItemRequestInternalChannelContactCtaUrl, optional, nullable)
- `internalChannelContactCtaLabel` (string, optional, nullable)
- `userAssignedFilterOptionsByFilter` (list of BulkPartnerEntryFilterOptionsRequest, optional)
- `certifications` (list of BulkPartnerEntryCertificationRequest, optional)

### BulkPartnerEntryUpdateResultItem

- `entryId` (string, required, nullable)
- `externalId` (string, required, nullable)
- `action` (enum, required) — * `created` - created * `updated` - updated * `unchanged` - unchanged * `failed` - failed
  - Allowed values: `created`, `updated`, `unchanged`, `failed`
- `entry` (ExternalPartnerEntrySerializerReader, optional, nullable) — Base Entry Serializer. Fields common to read and write operations of all entry types.
- `message` (string, optional, nullable)
- `errors` (any, optional, nullable)

### ExternalPartnerEntryBadgeRequest

A badge to assign; other keys of a read badge, such as order, are ignored.

- `id` (string, required)

### PatchedBulkPartnerEntryUpdateItemRequestInternalPointOfContactCtaUrl

- string
- string

### PatchedBulkPartnerEntryUpdateItemRequestInternalChannelContactCtaUrl

- string
- string

### BulkPartnerEntryFilterOptionsRequest

- `filterId` (string, required)
- `optionIds` (list of string, required)

### BulkPartnerEntryCertificationRequest

- `certificationId` (string, required)
- `quantity` (integer, optional)

### ExternalPartnerEntrySerializerReader

Base Entry Serializer. Fields common to read and write operations of all entry types.

- `href` (string, required)
- `id` (string, required)
- `title` (string, required)
- `headline` (string, required, nullable)
- `type` (string, required)
- `slug` (string, required)
- `customSlug` (string, required, nullable)
- `status` (enum, required) — * `hidden` - hidden * `inactive` - inactive * `public` - public * `searchable` - searchable
  - Allowed values: `hidden`, `inactive`, `public`, `searchable`
- `icon` (string, required, nullable)
- `image` (string, required, nullable)
- `iconDark` (string, required, nullable)
- `imageDark` (string, required, nullable)
- `website` (string, required, nullable)
- `userAssignedFilterOptions` (string, required)
- `systemAssignedFilterOptions` (string, required)
- `mappedAssignedFilterOptions` (string, required)
- `canSubmitForReview` (boolean, required)
- `readyForReview` (boolean, required)
- `externalId` (string, required, nullable)
- `description` (string, required, nullable)
- `location` (string, required, nullable)
- `callToActionUrl` (string, required, nullable)
- `callToActionLabel` (string, required, nullable)
- `externalUrl` (string, required, nullable)
- `awards` (list of AwardSerializerReader, required)
- `badges` (list of map from string to any, required) — Assigned badges in badge order. Each badge links to its assignment on the entry, so the entry id comes from the data, not the serializer context.
- `directory` (string, required)
- `approvalFlow` (string, required)
- `supportEmail` (string, required, nullable)
- `developerName` (string, required, nullable)
- `availability` (boolean, required, nullable)
- `lastModified` (LastModified, optional, nullable) — Serializer for last_modified info including timestamp and user details.
- `umamiShareId` (string, optional, nullable)
- `integrationDescription` (string, optional, nullable)
- `iconUrl` (string, optional, nullable)
- `imageUrl` (string, optional, nullable)
- `embedTitle` (string, optional, nullable)
- `embedSrc` (string, optional, nullable)
- `profileFields` (map from string to any, optional)
- `embedHeight` (integer, optional, nullable)

### AwardSerializerReader

- `title` (string, required)
- `file` (string, required, nullable)
- `order` (integer, required)
- `id` (string, required)
- `fileUrl` (string, optional, nullable)

### LastModified

Serializer for last_modified info including timestamp and user details.

- `modified` (string, required, nullable)
- `user` (LastModifiedUser, required, nullable) — Serializer for the user info in last_modified.

### LastModifiedUser

Serializer for the user info in last_modified.

- `id` (string, required)
- `fullName` (string, required, nullable)
- `email` (string, required)
- `type` (string, required)

## Examples

**Request**

```json
{
  "entries": [
    {
      "title": "Acme Agency",
      "status": "public",
      "externalId": "0015g00000XyZabAAF",
      "tier": "6f1c2a8e-3b7d-4c55-9a2e-1d0b8f4e7c21",
      "certifications": [
        {
          "certificationId": "9b2e4f60-1c3a-4d8e-b7f5-2a6c0d9e8b14"
        },
        {
          "certificationId": "c4d1a7e2-5f9b-4e03-8a6d-7b2f1e0c9d35",
          "quantity": 2
        }
      ]
    },
    {
      "badges": [
        {
          "id": "5a7d2c91-8e4b-4f36-a1c0-3b9e6d2f7a48"
        },
        {
          "id": "e2b6f8a3-0d5c-4719-9e8a-6c4b1d3f5a27"
        }
      ],
      "entryId": "3e8a1f4c-7b2d-4a96-b0e5-9c1d2f6a8b73",
      "certifications": []
    }
  ]
}
```

**SDK Code**

```python Daily sync: create one partner, update another (application/json)
import requests

url = "https://v1.api.partnerpage.io/api/directory/private/v1alpha/external/directories/:directoryId/entries/partners/bulk/"

payload = { "entries": [
        {
            "title": "Acme Agency",
            "status": "public",
            "externalId": "0015g00000XyZabAAF",
            "tier": "6f1c2a8e-3b7d-4c55-9a2e-1d0b8f4e7c21",
            "certifications": [
                { "certificationId": "9b2e4f60-1c3a-4d8e-b7f5-2a6c0d9e8b14" },
                {
                    "certificationId": "c4d1a7e2-5f9b-4e03-8a6d-7b2f1e0c9d35",
                    "quantity": 2
                }
            ]
        },
        {
            "badges": [{ "id": "5a7d2c91-8e4b-4f36-a1c0-3b9e6d2f7a48" }, { "id": "e2b6f8a3-0d5c-4719-9e8a-6c4b1d3f5a27" }],
            "entryId": "3e8a1f4c-7b2d-4a96-b0e5-9c1d2f6a8b73",
            "certifications": []
        }
    ] }
headers = {
    "Authorization": "Token  <apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Daily sync: create one partner, update another (application/json)
const url = 'https://v1.api.partnerpage.io/api/directory/private/v1alpha/external/directories/:directoryId/entries/partners/bulk/';
const options = {
  method: 'POST',
  headers: {Authorization: 'Token  <apiKey>', 'Content-Type': 'application/json'},
  body: '{"entries":[{"title":"Acme Agency","status":"public","externalId":"0015g00000XyZabAAF","tier":"6f1c2a8e-3b7d-4c55-9a2e-1d0b8f4e7c21","certifications":[{"certificationId":"9b2e4f60-1c3a-4d8e-b7f5-2a6c0d9e8b14"},{"certificationId":"c4d1a7e2-5f9b-4e03-8a6d-7b2f1e0c9d35","quantity":2}]},{"badges":[{"id":"5a7d2c91-8e4b-4f36-a1c0-3b9e6d2f7a48"},{"id":"e2b6f8a3-0d5c-4719-9e8a-6c4b1d3f5a27"}],"entryId":"3e8a1f4c-7b2d-4a96-b0e5-9c1d2f6a8b73","certifications":[]}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```