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.tsexportconstUsers: 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:
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.tsaccess: {
read: ({ req }) => {
if (req.user) {
// Super admin can read allif (isSuperAdmin(req.user)) returntrue;
// Customer can only read their own ordersif (req.user.collection === 'customers') {
return {
customer: {
equals: req.user.id,
},
};
}
}
returnfalse;
},
create: () =>true, // Public order creation for checkoutupdate: 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.
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:
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:
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:
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:
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:
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:
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:
code
select[fieldName]=true
To fetch only the order number and status from orders:
For nested fields within groups, you extend the bracket notation to specify the path:
code
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:
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:
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:
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.