---
title: "Mastering Payload CMS API: Authentication & Queries Explained"
slug: "fetch-payload-cms-data-rest-api"
published: "2025-11-05"
updated: "2025-12-25"
validated: "2025-11-12"
categories:
  - "Payload"
tags:
  - "Payload CMS"
  - "REST API"
  - "API authentication"
  - "query parameters"
  - "data fetching"
  - "n8n automation"
  - "MongoDB queries"
  - "API key setup"
llm-intent: "reference"
audience-level: "intermediate"
framework-versions:
  - "payload@2 (inferred)"
  - "node@18+ (recommended)"
  - "mongodb@6+ (recommended)"
status: "stable"
llm-purpose: "Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!"
llm-prereqs:
  - "Access to n8n"
  - "Access to cURL"
  - "Access to Postman"
llm-outputs:
  - "Completed outcome: Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!"
---

**Summary Triples**
- (Payload CMS, auto-generates REST endpoints for each collection, /api/{collectionSlug} and related CRUD routes are available by default)
- (Authentication, must be configured correctly for API keys, Enable API keys in Payload config (e.g., users collection) and use the required Authorization format when calling endpoints)
- (Common error, permission errors can be caused by wrong authentication format, Even valid API keys will be rejected if the request header uses the incorrect scheme)
- (n8n automations, can fail when authentication format is incorrect, Use the same Authorization pattern that works with curl/Postman in n8n HTTP request nodes)
- (Querying, use Payload's query parameters to fetch specific data, Apply filters, pagination, sorting and depth options via query parameters to limit returned data)

### {GOAL}
Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!

### {PREREQS}
- Access to n8n
- Access to cURL
- Access to Postman

### {STEPS}
1. Set up API Key in Payload
2. Authenticate API Requests
3. Fetch Data with cURL
4. Filter Data with Query Parameters
5. Implementing Automation with n8n

<!-- llm:goal="Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!" -->
<!-- llm:prereq="Access to n8n" -->
<!-- llm:prereq="Access to cURL" -->
<!-- llm:prereq="Access to Postman" -->
<!-- llm:output="Completed outcome: Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!" -->

# Mastering Payload CMS API: Authentication & Queries Explained
> Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!
Matija Žiberna · 2025-11-05

I was setting up an n8n automation to generate delivery reports from our Payload CMS database when I hit a wall. The API kept returning "Nimate dovoljenja za izvedbo tega dejanja" (You don't have permission to perform this action), even though I was using a valid API key from a super admin account. After digging through logs and documentation, I discovered the issue wasn't my permissions—it was how I was authenticating.

Payload CMS provides REST API endpoints for every collection automatically, but the authentication format is specific and easy to get wrong. This guide shows you exactly how to authenticate with Payload's API and use its powerful query parameters to fetch exactly the data you need.

## Understanding Payload's Built-In REST API

When you create a collection in Payload, the CMS automatically generates REST endpoints for you. No custom endpoint creation needed. For a collection with slug `delivery-dates`, you immediately get:

- `GET /api/delivery-dates` - List all documents
- `GET /api/delivery-dates/:id` - Get single document
- `POST /api/delivery-dates` - Create document
- `PATCH /api/delivery-dates/:id` - Update document
- `DELETE /api/delivery-dates/:id` - Delete document

The challenge isn't the endpoints themselves—they're already there. The challenge is understanding how to authenticate properly and how to filter data using Payload's query parameters.

## Setting Up API Key Authentication

Before you can fetch data externally, you need to enable API keys in your Payload configuration. In your collection config where authentication is enabled (typically your `users` collection), you need this setting:

```typescript
// File: src/collections/Users.ts
export const Users: CollectionConfig = {
  slug: 'users',
  auth: {
    useAPIKey: true,  // This enables API key authentication
    // ... other auth settings
  },
  // ... rest of configuration
}
```

Once enabled, navigate to your Payload admin panel, go to Users, and edit a super admin user. You'll see an API Key field where you can generate a new key. This is a long string like `77c9b8b0-1b81-46a1-ab62-86f76f427eed`. Copy this key—you'll need it for every API request.

The critical piece that took me hours to figure out is the authentication header format. Payload requires a very specific structure that's different from typical Bearer token authentication. The format is:

```
Authorization: {collection-slug} API-Key {your-api-key}
```

Notice the space between "API-Key" and your key, and the exact capitalization. For a users collection, your header looks like this:

```
Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed
```

This tells Payload to authenticate the request using the API key from the `users` collection, and Payload's middleware will populate `req.user` with your user document, allowing access control to work properly.

## Understanding Access Control Impact

Access control in Payload directly affects what your API requests can retrieve. Here's an example from an orders collection:

```typescript
// File: src/collections/Orders.ts
access: {
  read: ({ req }) => {
    if (req.user) {
      // Super admin can read all
      if (isSuperAdmin(req.user)) return true;

      // Customer can only read their own orders
      if (req.user.collection === 'customers') {
        return {
          customer: {
            equals: req.user.id,
          },
        };
      }
    }
    return false;
  },
  
  create: () => true,  // Public order creation for checkout
  
  update: superAdminOrTenantAdminAccess,
  delete: superAdminOrTenantAdminAccess,
}
```

This access control means unauthenticated requests will fail. A customer's API key will only return their own orders. Only a super admin's API key can retrieve all orders. This is why getting authentication right is crucial—without `req.user` being populated, Payload returns permission errors even for valid requests.

Compare this with a publicly readable collection:

```typescript
// File: src/collections/DeliveryDates.ts
access: {
  read: () => true,  // Anyone can read delivery dates
  create: superAdminOrTenantAdminAccess,
  update: superAdminOrTenantAdminAccess,
  delete: superAdminOrTenantAdminAccess,
}
```

Delivery dates can be fetched without authentication because `read` returns `true` for everyone. But you still need authentication to create, update, or delete records.

## Making Your First Authenticated Request

Let's fetch delivery dates with proper authentication. Using cURL:

```bash
curl -g 'https://www.kmetijamehak.si/api/delivery-dates' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

The `-g` flag tells cURL to disable URL globbing, which prevents issues with square brackets in query parameters we'll use later.

In Postman, set up the request like this:

1. Method: GET
2. URL: `https://www.kmetijamehak.si/api/delivery-dates`
3. Headers tab:
   - Key: `Authorization`
   - Value: `users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed`

For n8n or other automation tools, use the HTTP Request node with a custom header. The header name is `Authorization` and the value follows the same format: `users API-Key {your-key}`.

## Using the Where Parameter for Filtering

Payload's `where` parameter lets you filter documents using a MongoDB-style query syntax. The key is understanding how to structure these queries in URL parameters.

Let's say you want to fetch orders for a specific delivery date. Your delivery date has an ID of 5. The query looks like this:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[deliveryDate][equals]=5' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

The `where[deliveryDate][equals]=5` syntax tells Payload to filter orders where the `deliveryDate` relationship equals the ID 5. This returns only orders associated with that specific delivery date.

Payload supports multiple operators for different field types:

For relationships and basic equality:
```
where[fieldName][equals]=value
where[fieldName][not_equals]=value
```

For numeric and date fields:
```
where[price][greater_than]=100
where[price][less_than_equal]=500
where[date][greater_than_equal]=2025-11-05T00:00:00.000Z
```

For text fields:
```
where[status][in]=confirmed,completed
where[region][not_in]=cancelled
```

You can combine multiple where conditions. To fetch confirmed orders for a specific delivery date:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[deliveryDate][equals]=5&where[status][equals]=confirmed' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

Each additional where condition is added with an ampersand. Payload treats multiple conditions as AND operations by default.

## Filtering Date Fields Correctly

Date fields require special attention because they store full timestamps, not just dates. If your delivery date field contains `2025-11-05T12:00:00.000Z`, a query for `where[date][equals]=2025-11-05` will return nothing because it's not an exact match.

The solution is using range queries. To get all records for November 5th, 2025:

```bash
curl -g 'https://www.kmetijamehak.si/api/delivery-dates?where[date][greater_than_equal]=2025-11-05T00:00:00.000Z&where[date][less_than]=2025-11-06T00:00:00.000Z' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

This query finds all documents where the date is greater than or equal to the start of November 5th and less than the start of November 6th. This captures any time during that day, regardless of the specific timestamp stored.

Alternatively, if you know the exact timestamp stored in your database, you can match it precisely:

```bash
curl -g 'https://www.kmetijamehak.si/api/delivery-dates?where[date][equals]=2025-11-05T12:00:00.000Z' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

The range approach is more flexible when you're filtering by day rather than exact time.

## Populating Relationships with Depth

By default, Payload returns relationship fields as just IDs. If your order has a `deliveryDate` field that references the delivery-dates collection, you'll receive:

```json
{
  "id": 123,
  "orderNumber": "ORD-001",
  "deliveryDate": 5,
  "customer": 42
}
```

To get the full related documents, use the `depth` parameter:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[deliveryDate][equals]=5&depth=2' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

With `depth=2`, you get nested relationship data:

```json
{
  "id": 123,
  "orderNumber": "ORD-001",
  "deliveryDate": {
    "id": 5,
    "date": "2025-11-05T12:00:00.000Z",
    "region": "primorska",
    "locationTimeSlots": [
      {
        "location": {
          "id": 7,
          "name": "Ljubljana - parkirišče Barje p+r",
          "address": "Parkirišče Barje p+r, Ljubljana"
        },
        "timeStart": "2025-10-31T13:00:00.000Z",
        "timeEnd": "2025-10-31T13:30:00.000Z"
      }
    ]
  },
  "customer": {
    "id": 42,
    "email": "customer@example.com",
    "firstName": "John"
  }
}
```

The depth value determines how many levels deep to populate. `depth=1` populates immediate relationships. `depth=2` populates relationships within those relationships. Higher depth values can slow your queries, so use the minimum needed for your use case.

## Selecting Specific Fields with Select

When you're building reports or integrations, you often don't need every field from a document. Fetching unnecessary data wastes bandwidth and slows down your queries. Payload's `select` parameter lets you specify exactly which fields to return.

The select syntax uses bracket notation similar to where clauses. For top-level fields, the format is straightforward:

```
select[fieldName]=true
```

To fetch only the order number and status from orders:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?select[orderNumber]=true&select[status]=true' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

For nested fields within groups, you extend the bracket notation to specify the path:

```
select[groupName][fieldName]=true
```

Here's a real example from an orders collection. The `customerData` field is a group containing firstName, lastName, email, and other customer information. To select only specific fields from this group along with other top-level fields:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[deliveryDate][equals]=33&select[customerData][firstName]=true&select[customerData][email]=true&select[orderNumber]=true&select[deliveryDate][id]=true&select[deliveryDate][date]=true&select[pickupLocation]=true' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

This query returns orders for delivery date 33, but only includes:
- The customer's first name from the customerData group
- The customer's email from the customerData group
- The order number
- The delivery date's ID and date fields
- The pickup location

The response will look like:

```json
{
  "docs": [
    {
      "orderNumber": "ORD-001",
      "customerData": {
        "firstName": "John",
        "email": "john@example.com"
      },
      "deliveryDate": {
        "id": 33,
        "date": "2025-11-05T12:00:00.000Z"
      },
      "pickupLocation": 7
    }
  ]
}
```

Notice that other fields from customerData like lastName, phone, and address aren't included. Only the fields you explicitly selected are returned. This is particularly valuable when working with large collections or when building specific reports that need a subset of data.

When selecting fields from relationship documents like deliveryDate, you're choosing which fields from that related document to include. If you want the full related document, use the depth parameter instead. If you want specific fields from the relationship, combine select with depth:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[deliveryDate][equals]=33&depth=1&select[orderNumber]=true&select[deliveryDate][date]=true&select[deliveryDate][region]=true' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

This populates the deliveryDate relationship but only includes the date and region fields from it, along with the order number from the order itself.

## Other Useful Query Parameters

Beyond where, depth, and select, Payload provides several other query parameters to shape your response:

Limit the number of results:
```
limit=50
```

Paginate through results:
```
limit=50&page=2
```

Sort by field (ascending or descending):
```
sort=createdAt
sort=-createdAt
```

The minus sign indicates descending order. You can sort by any field in your collection. Combine all these parameters together for precise queries:

```bash
curl -g 'https://www.kmetijamehak.si/api/orders?where[status][equals]=confirmed&depth=1&limit=20&sort=-createdAt&select[orderNumber]=true&select[customer]=true&select[grandTotal]=true' \
  -H 'Authorization: users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed'
```

This query fetches the 20 most recent confirmed orders with customer details populated, returning only the order number, customer data, and grand total.

## Implementing in n8n

To use this in n8n for automated reports or cron jobs, add an HTTP Request node with these settings:

- Method: GET
- URL: `https://www.kmetijamehak.si/api/orders`
- Add Query Parameters:
  - Name: `where[deliveryDate][equals]`, Value: `33`
  - Name: `select[orderNumber]`, Value: `true`
  - Name: `select[customerData][firstName]`, Value: `true`
  - Name: `select[customerData][email]`, Value: `true`
  - Name: `select[deliveryDate][date]`, Value: `true`
  - Name: `depth`, Value: `1`
- Add Header:
  - Name: `Authorization`
  - Value: `users API-Key 77c9b8b0-1b81-46a1-ab62-86f76f427eed`

The response will be JSON that you can process in subsequent n8n nodes. Parse the `docs` array to access individual orders, then transform or export the data as needed for your reports. The select parameters ensure you're only fetching the exact data you need, making your automation faster and more efficient.

## Conclusion

Fetching data from Payload CMS externally requires understanding two key pieces: the specific authentication header format and how to structure query parameters. Once you know that authentication follows the `{collection-slug} API-Key {key}` pattern and that where clauses and select statements use bracket notation for nested filtering, you can query any Payload collection from n8n, cron jobs, or any external service.

You now have the foundation to build automated reports, sync data with external systems, or create custom integrations that leverage Payload's built-in REST API without writing a single custom endpoint. The access control you define in your collections will automatically apply to these API requests, keeping your data secure while giving you the flexibility to access it programmatically.

Let me know in the comments if you have questions, and subscribe for more practical development guides.

Thanks, Matija

## LLM Response Snippet
```json
{
  "goal": "Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!",
  "responses": [
    {
      "question": "What does the article \"Mastering Payload CMS API: Authentication & Queries Explained\" cover?",
      "answer": "Unlock Payload CMS API potential! Master authentication and querying techniques to fetch data efficiently. Learn more now!"
    }
  ]
}
```