The Nexera API is organised around REST. It accepts JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs. All API access is over HTTPS.
Base URL
https://api.nexerapxm.nl/api/v1
The Nexera API supports two authentication methods. JWT Bearer tokens are issued via the passwordless login flow and expire after 15 minutes. API keys are long-lived, tenant-scoped tokens created in Settings → API Keys and prefixed with pam_.
Authorization header
# JWT Bearer token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# API key
X-API-Key: pam_live_a1b2c3d4e5f6...General: 100 requests · 1 minute
Auth endpoints: 10 requests · 1 minute
Authenticated: 1,000 requests · 1 minute (per tenant)
Rate-limited responses return 429 Too Many Requests with a Retry-After header indicating when you can retry.
All errors follow a consistent JSON structure. The code field is a machine-readable identifier and the message field is a human-readable explanation.
Error response
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Field 'name' is required",
"details": {
"field": "name",
"rule": "required"
}
}
}200: OK, request succeeded.
201: Created, resource successfully created.
400: Bad Request, invalid parameters or request body.
401: Unauthorized, missing or invalid authentication.
403: Forbidden, insufficient permissions or plan limit.
404: Not Found, resource does not exist.
409: Conflict, duplicate resource or version conflict.
429: Too Many Requests, rate limit exceeded.
500: Internal Server Error, unexpected error on our end.
Nexera uses passwordless authentication. Users receive a one-time code via email, exchange it for a JWT token pair, and use the short-lived access token for subsequent requests. Refresh tokens allow seamless renewal without re-login.
POST /api/v1/auth/login
Sends a 6-digit OTP to the provided email address. Returns a success message regardless of whether the email exists, to prevent enumeration.
email: string · requiredUser email address.
200 OK
{
"data": {
"message": "Verification code sent"
}
}POST /api/v1/auth/verify
Validates the OTP code and returns a JWT access token, refresh token, and user profile. The access token expires in 15 minutes.
email: string · requiredUser email address.
code: string · required6-digit verification code from email.
200 OK
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4...",
"user": {
"id": "clx1abc23def456",
"email": "[email protected]",
"name": "Jan de Vries",
"role": "TENANT_ADMIN"
},
"tenant": {
"id": "clx0ten01abc789",
"name": "Rehall",
"plan": "PRO"
}
}
}POST /api/v1/auth/refresh
Exchanges a valid refresh token for a new access token and refresh token pair. The old refresh token is revoked (rotation).
refreshToken: string · requiredCurrent refresh token.
200 OK
{
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "bmV3IHJlZnJlc2ggdG9rZW4gYWZ0ZXIgcm90YXRpb24..."
}
}POST /api/v1/auth/logout
Revokes the provided refresh token, effectively logging the user out. The access token remains valid until it expires.
refreshToken: string · requiredRefresh token to revoke.
200 OK
{
"data": {
"message": "Logged out"
}
}Products are the core resource in Nexera. Each product contains structured data including multilingual titles and descriptions, SKU, EAN, pricing, variants, categories, and custom attributes. Products move through a status workflow: Draft → Active → Archived.
GET /api/v1/products
Returns a paginated list of products in your workspace. Supports filtering by status, category, brand, and full-text search across name, SKU, and description.
Requires JWT
page: integer · Page number. Default: 1.
limit: integer · Items per page. Default: 50, max: 100.
status: string · Filter by status: DRAFT, ACTIVE, or ARCHIVED.
search: string · Full-text search on name, SKU, and description.
categoryId: string · Filter by category UUID.
brandId: string · Filter by brand UUID.
sortBy: string · Sort field. Default: createdAt.
sortOrder: string · Sort direction: asc or desc. Default: desc.
200 OK
{
"data": [
{
"id": "clx1abc23def456",
"title": { "en": "Alpine Pro Jacket", "nl": "Alpine Pro Jas", "de": "Alpine Pro Jacke" },
"sku": "APJ-001",
"ean": "8712345678901",
"status": "ACTIVE",
"brandId": "clx0brd01abc789",
"completeness": 87,
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 142,
"totalPages": 3
}
}POST /api/v1/products
Creates a new product in Draft status. The title field supports multilingual content as a JSON object keyed by locale. Returns the full product object including generated ID.
Requires JWT
title: object · requiredMultilingual title, e.g. { "en": "Alpine Pro Jacket", "nl": "Alpine Pro Jas" }.
sku: string · Unique stock-keeping unit. Must be unique within your tenant.
ean: string · European Article Number (barcode).
description: object · Multilingual description as locale-keyed JSON.
status: string · Initial status: DRAFT (default), ACTIVE, or ARCHIVED.
categoryId: string · UUID of the primary category.
brandId: string · UUID of the brand.
201 Created
{
"data": {
"id": "clx2new01xyz789",
"title": { "en": "Alpine Pro Jacket", "nl": "Alpine Pro Jas" },
"sku": "APJ-001",
"ean": "8712345678901",
"status": "DRAFT",
"description": { "en": "Waterproof insulated jacket for alpine conditions." },
"brandId": "clx0brd01abc789",
"categoryId": "clx0cat01abc789",
"completeness": 42,
"variants": [],
"createdAt": "2025-02-08T09:15:00Z",
"updatedAt": "2025-02-08T09:15:00Z"
}
}GET /api/v1/products/:id
Returns a single product by ID, including its variants, category assignments, asset links, and computed completeness score.
Requires JWT
200 OK
{
"data": {
"id": "clx1abc23def456",
"title": { "en": "Alpine Pro Jacket", "nl": "Alpine Pro Jas", "de": "Alpine Pro Jacke" },
"sku": "APJ-001",
"ean": "8712345678901",
"status": "ACTIVE",
"description": {
"en": "Waterproof insulated jacket designed for alpine conditions.",
"nl": "Waterdichte geisoleerde jas voor alpine omstandigheden."
},
"brandId": "clx0brd01abc789",
"completeness": 87,
"variants": [
{
"id": "clx1var01abc123",
"sku": "APJ-001-M-BLK",
"title": "M / Black",
"price": 249.99,
"currency": "EUR",
"stock": 34
}
],
"categories": [
{ "categoryId": "clx0cat01abc789", "name": "Jackets" }
],
"createdAt": "2025-01-15T10:30:00Z",
"updatedAt": "2025-02-01T14:22:00Z"
}
}PATCH /api/v1/products/:id
Updates one or more fields on a product. Only provided fields are changed; omitted fields remain untouched. Creates a new version snapshot for rollback.
Requires JWT
title: object · Multilingual title object.
sku: string · Updated SKU.
ean: string · Updated EAN.
description: object · Multilingual description.
status: string · New status: DRAFT, ACTIVE, or ARCHIVED.
categoryId: string · Primary category UUID.
brandId: string · Brand UUID.
200 OK
{
"data": {
"id": "clx1abc23def456",
"title": { "en": "Alpine Pro Jacket v2", "nl": "Alpine Pro Jas v2" },
"sku": "APJ-001",
"status": "ACTIVE",
"completeness": 92,
"updatedAt": "2025-02-08T16:45:00Z"
}
}DELETE /api/v1/products/:id
Soft-deletes a product. The product is marked as deleted but can be restored by support. All linked variants and asset associations are preserved.
Requires JWT
200 OK
{
"data": {
"message": "Product deleted"
}
}The Digital Asset Management (DAM) module handles file uploads, storage, format conversion, and signed URL generation. Assets are stored in Cloudflare R2 with automatic metadata extraction, thumbnail generation, and optional AI-powered alt text.
GET /api/v1/assets
Returns a paginated list of assets in your workspace. Supports filtering by folder, tag, and full-text search.
Requires JWT
page: integer · Page number. Default: 1.
limit: integer · Items per page. Default: 50, max: 100.
folderId: string · Filter by folder UUID.
search: string · Search by filename or alt text.
mimeType: string · Filter by MIME type prefix, e.g. image/.
200 OK
{
"data": [
{
"id": "clx1ast01img123",
"filename": "APJ-001-front.jpg",
"mimeType": "image/jpeg",
"size": 2458624,
"width": 2400,
"height": 3200,
"altText": "Alpine Pro Jacket front view in black",
"folderId": "clx0fld01abc789",
"createdAt": "2025-01-20T11:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 847,
"totalPages": 17
}
}POST /api/v1/assets/upload
Uploads a single file using multipart/form-data. Accepted formats: JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG. Maximum file size is 50 MB. The file is processed asynchronously for thumbnail generation and metadata extraction.
Requires JWT
file: file · requiredThe file to upload (multipart/form-data).
folderId: string · Target folder UUID. Defaults to root.
201 Created
{
"data": {
"id": "clx2ast02img456",
"filename": "APJ-001-detail.png",
"mimeType": "image/png",
"size": 3145728,
"width": 1920,
"height": 1080,
"r2Key": "tenants/clx0ten01/assets/clx2ast02img456.png",
"folderId": null,
"createdAt": "2025-02-08T10:30:00Z"
}
}Multipart upload
Send the file as
multipart/form-datawith the field namefile. ThefolderIdcan be included as a form field alongside the file.
GET /api/v1/assets/:id/download
Generates a time-limited signed URL for downloading the original asset file. The URL expires after 1 hour.
Requires JWT
200 OK
{
"data": {
"url": "https://r2.nexera.app/tenants/clx0ten01/assets/clx1ast01img123.jpg?X-Amz-Signature=...",
"expiresAt": "2025-02-08T11:30:00Z"
}
}PATCH /api/v1/assets/:id
Updates editable metadata on an asset, such as alt text, tags, or folder assignment.
Requires JWT
altText: string · Alt text for accessibility and SEO.
folderId: string · Move asset to a different folder.
tags: string[] · Array of tag strings.
200 OK
{
"data": {
"id": "clx1ast01img123",
"filename": "APJ-001-front.jpg",
"altText": "Alpine Pro Jacket front view on white background",
"folderId": "clx0fld01abc789",
"tags": ["product-shot", "jacket", "hero"],
"updatedAt": "2025-02-08T12:00:00Z"
}
}DELETE /api/v1/assets/:id
Soft-deletes an asset. The file remains in storage for 30 days before permanent removal. Product associations are unlinked.
Requires JWT
200 OK
{
"data": {
"id": "clx1ast01img123",
"deletedAt": "2025-02-08T12:30:00Z"
}
}Categories organise products into a hierarchical tree structure. Each category can have a parent, allowing unlimited nesting. Products can belong to multiple categories.
GET /api/v1/categories
Returns the full category tree for your workspace as a nested array. Root categories have no parentId; child categories are nested under their parent.
Requires JWT
200 OK
{
"data": [
{
"id": "clx0cat01abc789",
"name": "Outerwear",
"parentId": null,
"productCount": 45,
"children": [
{
"id": "clx0cat02def012",
"name": "Jackets",
"parentId": "clx0cat01abc789",
"productCount": 28,
"children": []
},
{
"id": "clx0cat03ghi345",
"name": "Vests",
"parentId": "clx0cat01abc789",
"productCount": 17,
"children": []
}
]
},
{
"id": "clx0cat04jkl678",
"name": "Accessories",
"parentId": null,
"productCount": 62,
"children": []
}
]
}POST /api/v1/categories
Creates a new category. Provide a parentId to nest it under an existing category, or omit it to create a root-level category.
Requires JWT
name: string · requiredCategory display name.
parentId: string · Parent category UUID for nesting.
201 Created
{
"data": {
"id": "clx2cat05mno901",
"name": "Snowboard Jackets",
"parentId": "clx0cat02def012",
"productCount": 0,
"createdAt": "2025-02-08T13:00:00Z"
}
}PATCH /api/v1/categories/:id
Updates the name or parent of a category. Changing the parentId moves the category (and all descendants) within the tree.
Requires JWT
name: string · New display name.
parentId: string · New parent UUID, or null for root level.
200 OK
{
"data": {
"id": "clx2cat05mno901",
"name": "Snow Jackets",
"parentId": "clx0cat02def012",
"updatedAt": "2025-02-08T13:15:00Z"
}
}DELETE /api/v1/categories/:id
Deletes a category. Products assigned to this category are unlinked but not deleted. Child categories must be moved or deleted first.
Requires JWT
200 OK
{
"data": {
"message": "Category deleted"
}
}The sync engine pushes and pulls product data between Nexera and connected Shopify stores. Sync jobs run asynchronously via a background queue and support full syncs, delta syncs, and per-product operations.
POST /api/v1/shopify/sync/all
Initiates a full synchronisation job that pushes all active products to the specified Shopify store. Returns a job ID for tracking progress.
Requires JWT
storeId: string · requiredConnected Shopify store UUID.
200 OK
{
"data": {
"jobId": "clx2job01sync01",
"status": "QUEUED",
"type": "FULL",
"storeId": "clx0str01abc789",
"createdAt": "2025-02-08T14:00:00Z"
}
}POST /api/v1/shopify/sync/products
Pushes a specific set of products to the connected Shopify store. Useful for syncing individual products after edits.
Requires JWT
storeId: string · requiredConnected Shopify store UUID.
productIds: string[] · requiredArray of product UUIDs to sync.
200 OK
{
"data": {
"jobId": "clx2job02sync02",
"status": "QUEUED",
"productCount": 5,
"storeId": "clx0str01abc789",
"createdAt": "2025-02-08T14:10:00Z"
}
}GET /api/v1/shopify/sync/history
Returns paginated history of sync jobs for a specific store, ordered by most recent first.
Requires JWT
storeId: string · requiredShopify store UUID.
page: integer · Page number. Default: 1.
limit: integer · Items per page. Default: 25.
200 OK
{
"data": [
{
"jobId": "clx2job01sync01",
"status": "COMPLETED",
"type": "FULL",
"itemsSynced": 142,
"errors": 0,
"duration": 34500,
"startedAt": "2025-02-08T14:00:05Z",
"completedAt": "2025-02-08T14:00:39Z"
},
{
"jobId": "clx1job03sync03",
"status": "COMPLETED",
"type": "DELTA",
"itemsSynced": 8,
"errors": 1,
"duration": 2100,
"startedAt": "2025-02-07T09:00:02Z",
"completedAt": "2025-02-07T09:00:04Z"
}
],
"pagination": {
"page": 1,
"limit": 25,
"total": 47,
"totalPages": 2
}
}Manage Shopify store connections. Each tenant can connect multiple Shopify stores. The OAuth flow is initiated from Nexera and completes in the Shopify admin.
GET /api/v1/shopify/stores
Returns all Shopify stores connected to your workspace, including their sync status and configuration.
Requires JWT
200 OK
{
"data": [
{
"id": "clx0str01abc789",
"shopDomain": "rehall-outdoor.myshopify.com",
"shopName": "Rehall Outdoor",
"isActive": true,
"lastSyncAt": "2025-02-08T14:00:39Z",
"productCount": 142,
"createdAt": "2025-01-10T09:00:00Z"
}
]
}POST /api/v1/shopify/stores/connect
Initiates the Shopify OAuth authorization flow. Returns an authorization URL that the user must visit to install the Nexera app on their Shopify store.
Requires JWT
shop: string · requiredShopify store domain, e.g. my-store.myshopify.com.
200 OK
{
"data": {
"authUrl": "https://rehall-outdoor.myshopify.com/admin/oauth/authorize?client_id=...&scope=read_products,write_products&redirect_uri=..."
}
}DELETE /api/v1/shopify/stores/:id
Removes the Shopify store connection. This does not delete products from either Nexera or Shopify. Future syncs will stop until the store is reconnected.
Requires JWT
200 OK
{
"data": {
"message": "Store disconnected"
}
}Billing is managed through Stripe. These endpoints let you view your current subscription, manage plan changes, and access invoices. Payment method management is handled via the Stripe customer portal.
GET /api/v1/billing
Returns the current subscription details including plan, status, usage, and billing period.
Requires JWT
200 OK
{
"data": {
"plan": "PRO",
"status": "active",
"currentPeriodEnd": "2025-03-08T00:00:00Z",
"usage": {
"products": { "used": 142, "limit": 10000 },
"storage": { "usedBytes": 5368709120, "limitBytes": 53687091200 },
"users": { "used": 4, "limit": 25 },
"stores": { "used": 1, "limit": 5 }
}
}
}POST /api/v1/billing/checkout
Creates a Stripe Checkout session for subscribing to a plan or changing plans. Returns a URL to redirect the user to Stripe-hosted checkout.
Requires JWT
planId: string · requiredPlan identifier: FREE, BASIC, PRO, or ENTERPRISE.
200 OK
{
"data": {
"url": "https://checkout.stripe.com/c/pay/cs_live_a1b2c3...",
"sessionId": "cs_live_a1b2c3d4e5f6"
}
}GET /api/v1/billing/invoices
Returns recent invoices from Stripe for the current tenant.
Requires JWT
limit: integer · Number of invoices to return. Default: 10.
200 OK
{
"data": [
{
"id": "in_1abc2def3ghi",
"amount": 7900,
"currency": "eur",
"status": "paid",
"invoiceUrl": "https://invoice.stripe.com/i/acct_1abc/...",
"periodStart": "2025-01-08T00:00:00Z",
"periodEnd": "2025-02-08T00:00:00Z",
"createdAt": "2025-02-08T00:00:00Z"
}
]
}POST /api/v1/billing/portal
Creates a Stripe Customer Portal session where the user can manage payment methods, view invoices, and cancel subscriptions.
Requires JWT
200 OK
{
"data": {
"url": "https://billing.stripe.com/p/session/bps_1abc2def3ghi..."
}
}Manage team members within your workspace. Users are invited by email and assigned a role that determines their permissions. Roles follow a hierarchy: Viewer < Editor < Tenant Admin < Super Admin.
GET /api/v1/users
Returns all users in your workspace with their roles, status, and last login timestamp.
Requires JWT
200 OK
{
"data": [
{
"id": "clx1usr01abc123",
"email": "[email protected]",
"name": "Jan de Vries",
"role": "TENANT_ADMIN",
"status": "ACTIVE",
"lastLoginAt": "2025-02-08T09:15:00Z",
"createdAt": "2025-01-10T09:00:00Z"
},
{
"id": "clx1usr02def456",
"email": "[email protected]",
"name": "Lisa Bakker",
"role": "PRODUCT_MANAGER",
"status": "ACTIVE",
"lastLoginAt": "2025-02-07T16:30:00Z",
"createdAt": "2025-01-12T11:00:00Z"
}
]
}POST /api/v1/users
Sends an invitation email to a new team member. The user receives a verification link and is added to the workspace upon first login.
Requires JWT
email: string · requiredEmail address of the user to invite.
role: string · requiredRole to assign: VIEWER, PRODUCT_MANAGER, MARKETING, ASSET_MANAGER, SUPPLIER_MANAGER, FINANCE, or TENANT_ADMIN.
name: string · Display name for the invited user.
201 Created
{
"data": {
"id": "clx2usr03ghi789",
"email": "[email protected]",
"role": "PRODUCT_MANAGER",
"status": "INVITED",
"createdAt": "2025-02-08T15:00:00Z"
}
}PATCH /api/v1/users/:id
Changes the role of an existing team member. Only Tenant Admins can promote or demote users.
Requires JWT
role: string · requiredNew role: VIEWER, PRODUCT_MANAGER, MARKETING, ASSET_MANAGER, SUPPLIER_MANAGER, FINANCE, or TENANT_ADMIN.
200 OK
{
"data": {
"id": "clx1usr02def456",
"email": "[email protected]",
"role": "TENANT_ADMIN",
"updatedAt": "2025-02-08T15:30:00Z"
}
}DELETE /api/v1/users/:id
Removes a user from the workspace. The user loses access immediately. Their created content (products, assets) is preserved.
Requires JWT
200 OK
{
"data": {
"message": "User removed"
}
}API keys provide long-lived authentication for server-to-server integrations. Each key is scoped to your tenant and can be restricted to specific permissions. The full key value is only shown once at creation time.
GET /api/v1/api-keys
Returns all API keys for your workspace. The full key value is never returned after creation; only the prefix is shown for identification.
Requires JWT
200 OK
{
"data": [
{
"id": "clx0key01abc123",
"name": "Production Integration",
"keyPrefix": "pam_live_a1b2",
"permissions": ["product:read", "product:write", "asset:read"],
"lastUsedAt": "2025-02-08T14:00:00Z",
"createdAt": "2025-01-15T10:00:00Z"
},
{
"id": "clx0key02def456",
"name": "Staging Read-Only",
"keyPrefix": "pam_test_x9y8",
"permissions": ["product:read", "asset:read"],
"lastUsedAt": null,
"createdAt": "2025-02-01T09:00:00Z"
}
]
}POST /api/v1/api-keys
Creates a new API key. The full key value is returned in the response and cannot be retrieved again. Store it securely immediately.
Requires JWT
name: string · requiredHuman-readable label for the key.
permissions: string[] · requiredArray of permission scopes, e.g. ["product:read", "asset:read"].
201 Created
{
"data": {
"id": "clx2key03ghi789",
"name": "Webhook Integration",
"key": "pam_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"keyPrefix": "pam_live_a1b2",
"permissions": ["product:read", "product:write"],
"createdAt": "2025-02-08T16:00:00Z"
}
}Store your key securely
The full API key is only shown once in the creation response. Copy it immediately and store it in a secure location such as an environment variable or secrets manager. You will not be able to retrieve it again.
DELETE /api/v1/api-keys/:id
Permanently revokes an API key. Any requests using this key will immediately start receiving 401 responses.
Requires JWT
200 OK
{
"data": {
"message": "API key revoked"
}
}Official client libraries wrap the REST API with typed methods, automatic pagination, error handling, and token refresh. Install the SDK for your language to get started quickly.
N
JavaScript / TypeScript
npm install nexera-sdk
Py
Python 3.8+
pip install nexera
Node.js example
import { Nexera } from 'nexera-sdk';
const client = new Nexera({
apiKey: process.env.NEXERA_API_KEY,
baseUrl: 'https://api.nexerapxm.nl/api/v1',
});
// List active products
const products = await client.products.list({
status: 'ACTIVE',
limit: 25,
});
console.log(products.data);
console.log(products.pagination);