{
  "info": {
    "name": "Joryio API",
    "description": "REST API collection for the Joryio Marketing Automation Platform. Authenticate every request with `Authorization: Bearer {{token}}` — set `token` to an API key (`jry_live_...` / `jry_test_...`, created in Settings > API Keys or via the API Keys folder) or to a dashboard JWT. Full reference: https://docs.joryio.com/api/overview",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api-eu1.joryio.com",
      "type": "string"
    },
    {
      "key": "token",
      "value": "jry_live_your_api_key_here",
      "type": "string"
    },
    {
      "key": "organizationId",
      "value": "",
      "type": "string"
    },
    {
      "key": "workspaceId",
      "value": "",
      "type": "string"
    },
    {
      "key": "listId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Authentication",
      "description": "Dashboard account endpoints. For API integrations prefer an API key (`jry_live_...`) as the bearer token — API keys skip MFA/Turnstile and carry scoped permissions. Interactive login may additionally require MFA (`requiresMfa` + `tempToken` response) and, when Turnstile is configured, a `turnstileToken` body field.",
      "item": [
        {
          "name": "Register Organization",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const response = pm.response.json();",
                  "    if (response.organization) {",
                  "        pm.collectionVariables.set('organizationId', response.organization.id);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"admin@example.com\",\n  \"password\": \"Str0ng!Passw0rd42\",\n  \"organizationName\": \"My Company\",\n  \"firstName\": \"John\",\n  \"lastName\": \"Doe\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/register",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "register"
              ]
            },
            "description": "Create a new organization + admin user. Password policy: 12-128 chars with uppercase, lowercase, number, and special character. MFA setup is mandatory, so the response is { requiresMfaSetup: true, tempToken, user, organization } — complete MFA setup in the dashboard, then create an API key for integrations."
          }
        },
        {
          "name": "Login",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "if (pm.response.code === 200) {",
                  "    const response = pm.response.json();",
                  "    if (response.token) {",
                  "        pm.collectionVariables.set('token', response.token);",
                  "    }",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"admin@example.com\",\n  \"password\": \"Str0ng!Passw0rd42\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "login"
              ]
            },
            "description": "Dashboard login. On success returns { user, organization, workspaces, token } — the JWT is in `token`. Accounts with MFA enabled instead get { requiresMfa: true, tempToken }. When Turnstile is configured a `turnstileToken` body field is required, so scripted integrations should use an API key rather than this endpoint."
          }
        },
        {
          "name": "Get Profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "profile"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Users",
      "description": "User management API. Uses `userId` (your identifier) for creating users, and can reference users by either `userId` or Joryio's internal `id`.",
      "item": [
        {
          "name": "Create/Update User",
          "description": "Create a new user or update existing. Uses `userId` (your identifier) as the primary key.",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"externalId\": \"user-123\",\n  \"email\": \"user@example.com\",\n  \"phone\": \"+1234567890\",\n  \"attributes\": {\n    \"firstName\": \"Jane\",\n    \"lastName\": \"Smith\",\n    \"plan\": \"premium\"\n  },\n  \"subscriptions\": [\n    {\n      \"listId\": \"{{listId}}\",\n      \"channel\": \"email\"\n    }\n  ],\n  \"events\": [\n    {\n      \"name\": \"Order Completed\",\n      \"properties\": {\n        \"orderId\": \"ord_789\",\n        \"total\": 129.9,\n        \"currency\": \"USD\"\n      },\n      \"timestamp\": \"2026-01-15T10:29:45Z\"\n    }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users"
              ]
            },
            "description": "Create or update ONE user (object body). Optionally pass one-call onboarding fields: subscriptions (subscription-list memberships, max 100; each item {listId, channel?, status?}; channel defaults to email, status to subscribed; an existing opt-out is never silently resurrected - such items are skipped and reported) and events (max 25; each item {name, properties?, timestamp?}) ingested through the standard events pipeline (journey triggers, segments, analytics). The response gains itemized subscriptions {applied, skipped[]} and events {accepted, rejected[]} summaries when those inputs are provided; per-item failures never roll back the user upsert. The same endpoint also accepts a bare ARRAY body for bulk - see the 'Create/Update Users (array body)' request. NOTE: userId and externalId are NOT aliases. externalId is YOUR identifier (create-or-update key). userId is Joryio's internal 24-hex id (from API responses): it updates that exact user or returns 404, never creates, and never matches an externalId; non-24-hex userId values and bodies with both userId and externalId are rejected with 400."
          }
        },
        {
          "name": "Create/Update Users (array body)",
          "description": "Create a new user or update existing. Uses `userId` (your identifier) as the primary key.",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"externalId\": \"user-123\",\n    \"email\": \"user@example.com\",\n    \"attributes\": {\n      \"plan\": \"premium\"\n    },\n    \"subscriptions\": [\n      {\n        \"listId\": \"{{listId}}\",\n        \"channel\": \"email\"\n      }\n    ],\n    \"events\": [\n      {\n        \"name\": \"Order Completed\",\n        \"properties\": {\n          \"orderId\": \"ord_789\",\n          \"total\": 129.9,\n          \"currency\": \"USD\"\n        }\n      }\n    ]\n  },\n  {\n    \"userId\": \"user-456\",\n    \"email\": \"user2@example.com\",\n    \"attributes\": {\n      \"plan\": \"free\"\n    }\n  }\n]"
            },
            "url": {
              "raw": "{{baseUrl}}/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users"
              ]
            },
            "description": "Create or update MANY users in one call: POST /users with a bare JSON array body (max 1000 elements; no wrapper object). Each element is a full user object with the same fields as the single form, including the optional subscriptions/events onboarding fields (events capped at 25 per element). Every element is fully validated; invalid elements are reported in failed[] by index while valid elements still process. Response is a bulk summary: {processed, created, updated, failed: [{index, userId?, reason}], subscriptions: {applied, skipped}, events: {accepted, rejected}} - onboarding results aggregate as counts. NOTE: userId and externalId are NOT aliases. externalId is YOUR identifier (create-or-update key). userId is Joryio's internal 24-hex id (from API responses): it updates that exact user or returns 404, never creates, and never matches an externalId; non-24-hex userId values and bodies with both userId and externalId are rejected with 400."
          }
        },
        {
          "name": "Get All Users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users?limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            }
          }
        },
        {
          "name": "Get User by Joryio ID",
          "description": "Get user by Joryio's internal ID (the `id` field from API responses, a 24-character hex string)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c",
                  "description": "Joryio's internal user ID (24-char hex)"
                }
              ]
            }
          }
        },
        {
          "name": "Get User by Your User ID",
          "description": "Get user by your userId (the identifier you use to create/track users). This is the recommended lookup method.",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/by-user-id/:userId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                "by-user-id",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "user-123",
                  "description": "Your user identifier"
                }
              ]
            }
          }
        },
        {
          "name": "Update User by Joryio ID",
          "description": "Update user by Joryio's internal ID (the `id` field from API responses)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributes\": {\n    \"plan\": \"enterprise\",\n    \"lastSeen\": \"2026-01-04T12:00:00Z\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/users/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c",
                  "description": "Joryio's internal user ID (24-char hex)"
                }
              ]
            }
          }
        },
        {
          "name": "Update User by Your User ID",
          "description": "Update user by your userId. This is the recommended update method.",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributes\": {\n    \"plan\": \"enterprise\",\n    \"lastSeen\": \"2026-01-04T12:00:00Z\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/users/by-user-id/:userId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                "by-user-id",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "user-123",
                  "description": "Your user identifier"
                }
              ]
            }
          }
        },
        {
          "name": "Search Users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/search?query=jane&limit=10",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                "search"
              ],
              "query": [
                {
                  "key": "query",
                  "value": "jane"
                },
                {
                  "key": "limit",
                  "value": "10"
                }
              ]
            }
          }
        },
        {
          "name": "Delete User",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          },
          "description": "Delete a user. The user is moved to the recycle bin (archived with a who/when/why audit) and can be restored - it is not permanently erased. ClickHouse event history is retained. Pass an optional `?reason=` to record why."
        },
        {
          "name": "List Deleted Users (Recycle Bin)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/deleted?query=&limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                "deleted"
              ],
              "query": [
                {
                  "key": "query",
                  "value": "",
                  "description": "Match email/externalId (prefix), phone, or original id"
                },
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "List deleted users (the recycle bin) with the delete audit - who deleted each one, when, how, and why. Deleted users are archived, not lost."
          }
        }
      ]
    },
    {
      "name": "Events",
      "description": "Event tracking API. Identify users by `userId` (your identifier), `anonymousId`, or a `userAlias` object.",
      "item": [
        {
          "name": "Track Event (with userId)",
          "description": "Track event using your user identifier. This is the most common method.",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"eventName\": \"Order Completed\",\n  \"properties\": {\n    \"productId\": \"SKU-456\",\n    \"amount\": 99.99,\n    \"currency\": \"USD\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/events/track",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events",
                "track"
              ]
            },
            "description": "Track a single event (object body). This endpoint accepts two body forms: a single event object (this request - returns { eventId, success }) or a bare JSON array of event objects for batch tracking (max 500; see 'Track Events (array body)')."
          }
        },
        {
          "name": "Track Events (array body)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"userId\": \"user-123\",\n    \"eventName\": \"Product Viewed\",\n    \"properties\": {\n      \"productId\": \"SKU-456\",\n      \"price\": 49.99\n    }\n  },\n  {\n    \"userId\": \"user-123\",\n    \"eventName\": \"Order Completed\",\n    \"properties\": {\n      \"orderId\": \"ord_789\",\n      \"total\": 129.9,\n      \"currency\": \"USD\"\n    }\n  }\n]"
            },
            "url": {
              "raw": "{{baseUrl}}/events/track",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events",
                "track"
              ]
            },
            "description": "Track MANY events in one call: POST /events/track with a bare JSON array body (max 500 elements; no wrapper object). Each element follows the same format as the single-object form. Every element is fully validated; invalid elements are reported in rejected[] by index while valid elements still process. Response is an aggregate summary: {processed, accepted, rejected: [{index, name?, reason}]} - processed = elements received, accepted = events actually inserted."
          }
        },
        {
          "name": "Query Events",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/events/query?eventName=Order Completed&startDate=2026-01-01&endDate=2026-01-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "events",
                "query"
              ],
              "query": [
                {
                  "key": "eventName",
                  "value": "Order Completed"
                },
                {
                  "key": "startDate",
                  "value": "2026-01-01"
                },
                {
                  "key": "endDate",
                  "value": "2026-01-31"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "E-Commerce - Catalog",
      "description": "Product catalog management. Uses `productId` (your SKU/product ID) for identifying products.",
      "item": [
        {
          "name": "Create Product",
          "description": "Create a new product. Uses `productId` as your unique identifier. This endpoint accepts two body forms: a single product object (this request - returns the full product) or a bare JSON array of product objects for bulk upsert (max 500; see 'Create Products (array body)').",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"productId\": \"SKU-12345\",\n  \"name\": \"Classic Blue T-Shirt\",\n  \"price\": 29.99,\n  \"compareAtPrice\": 39.99,\n  \"currency\": \"USD\",\n  \"categories\": [\"Clothing\", \"T-Shirts\"],\n  \"tags\": [\"sale\", \"bestseller\"],\n  \"brand\": \"Acme Apparel\",\n  \"imageUrl\": \"https://example.com/images/blue-tshirt.jpg\",\n  \"url\": \"https://example.com/products/blue-tshirt\",\n  \"inStock\": true,\n  \"sku\": \"BTS-001-BL\",\n  \"variants\": [\n    {\n      \"id\": \"var-s\",\n      \"name\": \"Small\",\n      \"sku\": \"BTS-001-BL-S\",\n      \"price\": 29.99,\n      \"inStock\": true,\n      \"options\": { \"size\": \"S\", \"color\": \"Blue\" }\n    },\n    {\n      \"id\": \"var-m\",\n      \"name\": \"Medium\",\n      \"sku\": \"BTS-001-BL-M\",\n      \"price\": 29.99,\n      \"inStock\": true,\n      \"options\": { \"size\": \"M\", \"color\": \"Blue\" }\n    }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/catalog/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products"
              ]
            }
          }
        },
        {
          "name": "Create Products (array body)",
          "description": "Create or update multiple products at once with a bare JSON array body.",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"productId\": \"SKU-001\",\n    \"name\": \"Product 1\",\n    \"price\": 19.99\n  },\n  {\n    \"productId\": \"SKU-002\",\n    \"name\": \"Product 2\",\n    \"price\": 29.99,\n    \"source\": \"shopify\"\n  }\n]"
            },
            "url": {
              "raw": "{{baseUrl}}/catalog/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products"
              ]
            },
            "description": "Create or update MANY products in one call: POST /catalog/products with a bare JSON array body (max 500 elements; no wrapper object). Each element follows the same format as the single-object form and is upserted by productId. Every element is fully validated; invalid elements are reported in failed[] by index while valid elements still process. Response is an aggregate summary: {processed, upserted, failed: [{index, productId?, sku?, reason}]}."
          }
        },
        {
          "name": "List Products",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/products?limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                },
                {
                  "key": "categories",
                  "value": "T-Shirts",
                  "disabled": true
                },
                {
                  "key": "inStock",
                  "value": "true",
                  "disabled": true
                },
                {
                  "key": "brand",
                  "value": "Acme",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Get Product by Joryio ID",
          "description": "Get product using Joryio's internal ID (returned in API responses)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c",
                  "description": "Joryio's internal product ID (24-char hex)"
                }
              ]
            }
          }
        },
        {
          "name": "Get Product by Your Product ID",
          "description": "Get product using your productId (SKU, product ID from your e-commerce platform). This is the recommended lookup method.",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/products/by-product-id/:productId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products",
                "by-product-id",
                ":productId"
              ],
              "variable": [
                {
                  "key": "productId",
                  "value": "SKU-12345",
                  "description": "Your product identifier"
                }
              ]
            }
          }
        },
        {
          "name": "Update Product",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"price\": 24.99,\n  \"inStock\": false,\n  \"tags\": [\"clearance\"]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/catalog/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          }
        },
        {
          "name": "Delete Product",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "products",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          }
        },
        {
          "name": "Get Categories",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "categories"
              ]
            }
          }
        },
        {
          "name": "Get Brands",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/brands",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "brands"
              ]
            }
          }
        },
        {
          "name": "Get Catalog Stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/catalog/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "catalog",
                "stats"
              ]
            },
            "description": "Catalogue totals plus catalogue health, counted over EVERY product (not a page): productCount, categoryCount, brandCount, outOfStockCount, lowStockCount, unclassifiedCount (missing a category or a brand) and lowStockThreshold (the units-remaining boundary lowStockCount was computed at)."
          }
        }
      ]
    },
    {
      "name": "E-Commerce - Orders",
      "description": "Order management. Uses `orderId` (your order number) and identifies customers by `userId` (your user identifier).",
      "item": [
        {
          "name": "Create Order",
          "description": "Create an order for a user (identified by your `userId`). Set `totalRefunded` (order currency, default 0) to record refunds so revenue is netted and the refunded amount shows on the Orders page: for a partial refund send the partial amount with `status: partiallyRefunded`; for a full refund set it to `total` with `status: refunded`.",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"orderId\": \"ORD-2024-001\",\n  \"userId\": \"user-123\",\n  \"total\": 89.97,\n  \"subtotal\": 99.97,\n  \"discount\": 10.00,\n  \"shipping\": 5.99,\n  \"tax\": 4.99,\n  \"totalRefunded\": 0.00,\n  \"currency\": \"USD\",\n  \"couponCode\": \"SAVE10\",\n  \"items\": [\n    {\n      \"productId\": \"SKU-12345\",\n      \"name\": \"Blue T-Shirt\",\n      \"quantity\": 2,\n      \"price\": 29.99,\n      \"total\": 59.98\n    },\n    {\n      \"productId\": \"SKU-67890\",\n      \"name\": \"Black Jeans\",\n      \"quantity\": 1,\n      \"price\": 49.99,\n      \"total\": 49.99\n    }\n  ],\n  \"source\": \"email\",\n  \"campaignId\": \"camp_xyz789\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders"
              ]
            }
          }
        },
        {
          "name": "List Orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders?limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                },
                {
                  "key": "status",
                  "value": "pending",
                  "disabled": true
                },
                {
                  "key": "userId",
                  "value": "user-123",
                  "disabled": true
                },
                {
                  "key": "startDate",
                  "value": "2024-01-01",
                  "disabled": true
                },
                {
                  "key": "endDate",
                  "value": "2024-12-31",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Get Order by Joryio ID",
          "description": "Get order using Joryio's internal ID",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c",
                  "description": "Joryio's internal order ID (24-char hex)"
                }
              ]
            }
          }
        },
        {
          "name": "Get Order by Your Order ID",
          "description": "Get order using your orderId (order number from your e-commerce platform). This is the recommended lookup method.",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/by-order-id/:orderId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "by-order-id",
                ":orderId"
              ],
              "variable": [
                {
                  "key": "orderId",
                  "value": "ORD-2024-001",
                  "description": "Your order identifier"
                }
              ]
            }
          }
        },
        {
          "name": "Get User Orders",
          "description": "Get all orders for a specific user",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/user/:userId?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "user",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "user-123"
                }
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            }
          }
        },
        {
          "name": "Fulfill Order",
          "description": "Mark order as shipped with tracking info",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"trackingNumber\": \"1Z999AA10123456784\",\n  \"carrier\": \"UPS\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/orders/:id/fulfill",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                ":id",
                "fulfill"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          }
        },
        {
          "name": "Cancel Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Customer requested cancellation\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/orders/:id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                ":id",
                "cancel"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          }
        },
        {
          "name": "Refund Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refundAmount\": 29.99,\n  \"reason\": \"Product defective\",\n  \"partial\": true\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/orders/:id/refund",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                ":id",
                "refund"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            }
          }
        },
        {
          "name": "Get Order Stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/stats?startDate=2024-01-01&endDate=2024-01-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "stats"
              ],
              "query": [
                {
                  "key": "startDate",
                  "value": "2024-01-01"
                },
                {
                  "key": "endDate",
                  "value": "2024-01-31"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "E-Commerce - Carts",
      "description": "Cart tracking for abandoned cart recovery campaigns.",
      "item": [
        {
          "name": "Update Cart",
          "description": "Track or update a user's cart",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"items\": [\n    {\n      \"productId\": \"SKU-12345\",\n      \"name\": \"Blue T-Shirt\",\n      \"price\": 29.99,\n      \"quantity\": 2,\n      \"total\": 59.98,\n      \"imageUrl\": \"https://example.com/image.jpg\"\n    }\n  ],\n  \"value\": 59.98,\n  \"currency\": \"USD\",\n  \"checkoutUrl\": \"https://store.example.com/checkout?cart=abc123\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/carts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts"
              ]
            }
          }
        },
        {
          "name": "Add to Cart",
          "description": "Add an item to a user's cart",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"item\": {\n    \"productId\": \"SKU-12345\",\n    \"name\": \"Blue T-Shirt\",\n    \"price\": 29.99,\n    \"quantity\": 1,\n    \"total\": 29.99\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/carts/add",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "add"
              ]
            }
          }
        },
        {
          "name": "Remove from Cart",
          "description": "Remove an item from a user's cart",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"productId\": \"SKU-12345\",\n  \"quantity\": 1\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/carts/remove",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "remove"
              ]
            }
          }
        },
        {
          "name": "Get User Cart",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/carts/user/:userId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "user",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "user-123"
                }
              ]
            }
          }
        },
        {
          "name": "Clear Cart",
          "description": "Clear a user's cart (after purchase or manual clear)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"reason\": \"purchased\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/carts/clear",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "clear"
              ]
            }
          }
        },
        {
          "name": "Get Abandoned Carts",
          "description": "List abandoned carts for recovery campaigns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/carts/abandoned?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "abandoned"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "minValue",
                  "value": "50",
                  "disabled": true
                },
                {
                  "key": "abandonedMinutesAgo",
                  "value": "60",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Mark Cart Recovered",
          "description": "Mark abandoned cart as recovered (after purchase)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"orderId\": \"ORD-2024-001\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/carts/recovered",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "recovered"
              ]
            }
          }
        },
        {
          "name": "Get Cart Stats",
          "description": "Get cart and abandonment statistics",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/carts/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "carts",
                "stats"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "E-Commerce - RFM Analysis",
      "description": "RFM (Recency, Frequency, Monetary) customer segmentation analysis.",
      "item": [
        {
          "name": "Get User RFM Data",
          "description": "Get RFM scores and segment for a specific user",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rfm/user/:userId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rfm",
                "user",
                ":userId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "user-123"
                }
              ]
            }
          }
        },
        {
          "name": "Get RFM Distribution",
          "description": "Get customer distribution across RFM segments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rfm/distribution",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rfm",
                "distribution"
              ]
            }
          }
        },
        {
          "name": "Get RFM Segments",
          "description": "Get list of all RFM segments with descriptions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rfm/segments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rfm",
                "segments"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Segments",
      "item": [
        {
          "name": "Create Segment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Premium Users\",\n  \"description\": \"Users on premium plan\",\n  \"filterGroups\": [\n    {\n      \"filters\": [\n        {\n          \"type\": \"attribute\",\n          \"field\": \"plan\",\n          \"operator\": \"equals\",\n          \"value\": \"premium\"\n        }\n      ],\n      \"operator\": \"AND\"\n    }\n  ],\n  \"groupOperator\": \"AND\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/segments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "segments"
              ]
            },
            "description": "Create a segment. Audience is defined by `filterGroups` (each group = { filters: [...], operator: 'AND'|'OR' }, max 20 groups of 50 filters) combined by the required `groupOperator`; optional `excludeFilterGroups` subtracts matches. Filter `type` values include attribute, event, segment, subscription, entity, rfm, and more."
          }
        },
        {
          "name": "Get All Segments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/segments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "segments"
              ]
            }
          }
        },
        {
          "name": "Get Segment Size",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/segments/:segmentId/size",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "segments",
                ":segmentId",
                "size"
              ],
              "variable": [
                {
                  "key": "segmentId",
                  "value": ""
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Campaigns",
      "item": [
        {
          "name": "Create Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Welcome Email\",\n  \"description\": \"Welcome email for new users\",\n  \"channel\": \"email\",\n  \"sendType\": \"immediate\",\n  \"variants\": [\n    {\n      \"id\": \"variant-a\",\n      \"name\": \"Variant A\",\n      \"weight\": 100,\n      \"message\": {\n        \"subject\": \"Welcome to My Company!\",\n        \"from\": \"hello@example.com\",\n        \"fromName\": \"My Company\",\n        \"html\": \"<h1>Hi {{ user.firstName }}, welcome aboard!</h1>\"\n      }\n    }\n  ],\n  \"targeting\": {\n    \"filterGroups\": [\n      {\n        \"filters\": [\n          {\n            \"type\": \"attribute\",\n            \"field\": \"plan\",\n            \"operator\": \"equals\",\n            \"value\": \"premium\"\n          }\n        ],\n        \"operator\": \"AND\"\n      }\n    ]\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Create a campaign. Message content lives in `variants` (required for every channel except in_app): each variant has id, name, weight, and a channel-shaped `message` (email: subject/from/fromName + html or templateId; sms: body; viber: body/imageUrl/buttonCaption/buttonUrl + optional ttlSeconds and smsFallbackEnabled/smsFallbackBody; push: title/body; whatsapp: templateId/variableMapping). Audience is `targeting` with userIds and/or filterGroups (same filter shape as Segments) — there is no `segmentId` field. sendType: immediate|scheduled|triggered|intelligent|recurring|ai_optimized."
          }
        },
        {
          "name": "Get All Campaigns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ]
            }
          }
        },
        {
          "name": "Send Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "send"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Send Test Viber Message",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"+306912345678\",\n  \"userData\": {\n    \"firstName\": \"Maria\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/send-test-viber",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "send-test-viber"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Send a test Viber message for a viber campaign to a single phone number (no audience send). Renders the first variant (body/imageUrl/buttonCaption/buttonUrl + optional smsFallbackBody) with the optional `userData` as the personalization context. The recipient must not have opted out of Viber. Requires a Viber-capable provider account (Apifon) with a registered Viber sender."
          }
        },
        {
          "name": "Publish Campaign Changes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/publish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "publish"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Apply buffered edits made to a live (active) campaign onto the running campaign atomically, so subsequent sends use the new content. Returns 400 if there are no unpublished changes."
          }
        },
        {
          "name": "Discard Campaign Changes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/discard-draft",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "discard-draft"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Discard buffered edits to a live campaign so it reverts to its currently-published content. No-op if nothing is buffered."
          }
        },
        {
          "name": "Re-send Campaign (Send again)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/resend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "resend"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Re-send a completed one-time/scheduled campaign per its resendPolicy: only_new (skip prior recipients), everyone (include them), or cooldown (everyone except those messaged in the last resendCooldownDays days). Consent + suppression always enforced; frequency caps follow ignoreTouchingRules. 400 for triggered/recurring/in-app/ai_optimized or a campaign that hasn't finished sending."
          }
        },
        {
          "name": "Get Campaign Stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "stats"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Get Campaign Failure Reasons",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/failure-reasons",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "failure-reasons"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Delivery-failure breakdown for a campaign: message.failed events grouped by provider DLR error code, most common first. Returns [{ code, reason, count }] (reason is a best-effort humanized label; code is the raw provider code)."
          }
        },
        {
          "name": "Get Campaign Send Occurrences",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/send-occurrences",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "send-occurrences"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Per-send breakdown for a recurring campaign - the 'is this decaying?' view. Rows are keyed on SEND DATE, derived from the events themselves, so it covers campaigns that already ran. A campaign that sends more than once in a day has those sends combined in one row; `occurrence` is null while rows are date-derived (`derivedFromDates: true`). Engagement is attributed to the send that preceded it, bounded by that recipient's next send, so opens are never credited to the wrong send. Bots excluded. Rates use delivered as the denominator, falling back to sent when delivery is untracked."
          }
        },
        {
          "name": "Get Campaign Time to Engage",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/time-to-engage?startDate=2026-01-01&endDate=2026-01-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "time-to-engage"
              ],
              "query": [
                {
                  "key": "startDate",
                  "value": "2026-01-01",
                  "description": "Optional ISO date (inclusive)"
                },
                {
                  "key": "endDate",
                  "value": "2026-01-31",
                  "description": "Optional ISO date (inclusive)"
                }
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Distribution of how long recipients took to first open or click, measured PER RECIPIENT from their own send time - so it is meaningful for triggered campaigns, where every person has a different send moment. Bot engagements are excluded. Returns { buckets: [{ label, users, share, cumulative }], medianMinutes, engagers }; medianMinutes is interpolated from the bucket a cumulative 50% falls in, and is null when nobody has engaged."
          }
        },
        {
          "name": "Send Transactional Message",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"channel\": \"email\",\n  \"message\": {\n    \"subject\": \"Your order confirmation\",\n    \"html\": \"<p>Thank you for your order, {{ user.firstName }}!</p>\"\n  },\n  \"triggerData\": {\n    \"name\": \"Order Completed\",\n    \"properties\": {\n      \"orderId\": \"ORD-2024-001\",\n      \"total\": 99.99\n    }\n  },\n  \"idempotencyKey\": \"order-ORD-2024-001-confirmation\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns/transactional/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                "transactional",
                "send"
              ]
            },
            "description": "Send a one-off message to a single user without creating a campaign. channel: email|sms|push|viber. `message` carries subject (email subject / push title), body (plain text / sms / push body), html (email). Transactional Viber requires `viberTemplateId` — an APPROVED template from the Viber template registry (Rakuten Viber mandates pre-approved templates for transactional/OTP messages since 2026-07-01); the template body IS the message (`message` may be {}), and optional `variables` fill its dynamic fields. Optional `triggerData` ({ type, name, properties, metadata }) is available to personalization; optional `idempotencyKey` dedupes retries."
          }
        }
      ]
    },
    {
      "name": "Canvas",
      "item": [
        {
          "name": "Create Canvas",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Onboarding Flow\",\n  \"description\": \"Multi-step onboarding journey\",\n  \"sendType\": \"trigger\",\n  \"entryTrigger\": {\n    \"type\": \"event\",\n    \"config\": {\n      \"eventName\": \"user_signed_up\"\n    }\n  },\n  \"nodes\": [\n    {\n      \"id\": \"start\",\n      \"type\": \"trigger\",\n      \"config\": {\n        \"type\": \"event\",\n        \"eventName\": \"user_signed_up\"\n      },\n      \"position\": {\n        \"x\": 0,\n        \"y\": 0\n      }\n    },\n    {\n      \"id\": \"delay1\",\n      \"type\": \"delay\",\n      \"config\": {\n        \"delayType\": \"duration\",\n        \"value\": 1,\n        \"unit\": \"days\"\n      },\n      \"position\": {\n        \"x\": 200,\n        \"y\": 0\n      }\n    },\n    {\n      \"id\": \"email1\",\n      \"type\": \"email\",\n      \"config\": {\n        \"subject\": \"Welcome!\",\n        \"html\": \"<p>Thanks for signing up, {{ user.firstName }}</p>\"\n      },\n      \"position\": {\n        \"x\": 400,\n        \"y\": 0\n      }\n    }\n  ],\n  \"edges\": [\n    {\n      \"id\": \"e1\",\n      \"source\": \"start\",\n      \"target\": \"delay1\"\n    },\n    {\n      \"id\": \"e2\",\n      \"source\": \"delay1\",\n      \"target\": \"email1\"\n    }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/journeys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "journeys"
              ]
            },
            "description": "Create a journey (canvas). Each node is { id, type, config, position? } — node settings live in `config`, and `entryTrigger` is { type, config } (e.g. type 'event' with config.eventName). Valid entryTrigger types: event, segment, api, entity_change, whatsapp_inbound, sms_inbound, viber_inbound, attribute_change, subscription_status, schedule. Created journeys start as drafts — activate to go live."
          }
        },
        {
          "name": "Get All Canvases",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/journeys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "journeys"
              ]
            }
          }
        },
        {
          "name": "Activate Canvas",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/journeys/:canvasId/activate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "journeys",
                ":canvasId",
                "activate"
              ],
              "variable": [
                {
                  "key": "canvasId",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Get Canvas Stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/journeys/:canvasId/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "journeys",
                ":canvasId",
                "stats"
              ],
              "variable": [
                {
                  "key": "canvasId",
                  "value": ""
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Custom Entities",
      "item": [
        {
          "name": "Create Entity Definition",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"products\",\n  \"displayName\": \"Products\",\n  \"description\": \"Product catalog\",\n  \"fields\": [\n    {\n      \"name\": \"name\",\n      \"displayName\": \"Product Name\",\n      \"fieldType\": \"string\",\n      \"indexed\": true\n    },\n    {\n      \"name\": \"price\",\n      \"displayName\": \"Price\",\n      \"fieldType\": \"number\"\n    }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/entities",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "entities"
              ]
            }
          }
        },
        {
          "name": "Create Entity Record",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"data\": {\n    \"name\": \"Premium Widget\",\n    \"price\": 99.99,\n    \"sku\": \"WIDGET-001\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/entities/:entityId/records",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "entities",
                ":entityId",
                "records"
              ],
              "variable": [
                {
                  "key": "entityId",
                  "value": ""
                }
              ]
            },
            "description": "Create a single record (object body, `{ \"data\": { ... } }` envelope). This endpoint accepts two body forms: a single record object (this request - returns the full record) or a bare JSON array of record objects for bulk create (max 1000; see 'Create Entity Records (array body)')."
          }
        },
        {
          "name": "Create Entity Records (array body)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"data\": {\n      \"name\": \"Premium Widget\",\n      \"price\": 99.99,\n      \"sku\": \"WIDGET-001\"\n    }\n  },\n  {\n    \"data\": {\n      \"name\": \"Deluxe Widget\",\n      \"price\": 149.99,\n      \"sku\": \"WIDGET-002\"\n    }\n  }\n]"
            },
            "url": {
              "raw": "{{baseUrl}}/entities/:entityId/records?triggerAlerts=false",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "entities",
                ":entityId",
                "records"
              ],
              "query": [
                {
                  "key": "triggerAlerts",
                  "value": "false",
                  "description": "Set to true to fire relationship alerts (restock etc.) for records that transition in this import"
                }
              ],
              "variable": [
                {
                  "key": "entityId",
                  "value": ""
                }
              ]
            },
            "description": "Create MANY records in one call: POST /entities/:entityId/records with a bare JSON array body (max 1000 elements; no wrapper object). Each element uses the same `{ \"data\": { ... } }` envelope as the single-object form. Every element is validated; invalid elements are reported in failed[] by index while valid elements still process (field-definition validation applies to the whole batch). Response is an aggregate summary: {processed, inserted, insertedIds, failed: [{index, reason}]}."
          }
        },
        {
          "name": "Get Entity Records",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/entities/:entityId/records?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "entities",
                ":entityId",
                "records"
              ],
              "variable": [
                {
                  "key": "entityId",
                  "value": ""
                }
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "In-App Campaigns",
      "item": [
        {
          "name": "Create In-App Campaign (Event Trigger)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Reward Modal on Purchase\",\n  \"channel\": \"in_app\",\n  \"sendType\": \"triggered\",\n  \"channelConfig\": {\n    \"type\": \"modal\",\n    \"triggers\": [\n      {\n        \"type\": \"event\",\n        \"event\": \"Order Completed\"\n      }\n    ],\n    \"frequencyCap\": {\n      \"maxImpressions\": 1,\n      \"timeWindow\": \"day\"\n    },\n    \"priority\": 5,\n    \"liquidEnabled\": true\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Create an in-app campaign shown when the user performs an event. channelConfig.type: modal|banner|slideup|fullscreen|custom. Trigger types: immediate|event|attribute_change|attribute_threshold|push_notification_tap. frequencyCap.timeWindow: once|session|day|week|month|lifetime."
          }
        },
        {
          "name": "Create In-App Campaign (Immediate)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Welcome Banner\",\n  \"channel\": \"in_app\",\n  \"sendType\": \"triggered\",\n  \"channelConfig\": {\n    \"type\": \"banner\",\n    \"triggers\": [\n      {\n        \"type\": \"immediate\"\n      }\n    ],\n    \"frequencyCap\": {\n      \"maxImpressions\": 3,\n      \"timeWindow\": \"week\"\n    }\n  },\n  \"targeting\": {\n    \"filterGroups\": [\n      {\n        \"filters\": [\n          {\n            \"type\": \"attribute\",\n            \"field\": \"plan\",\n            \"operator\": \"equals\",\n            \"value\": \"free\"\n          }\n        ],\n        \"operator\": \"AND\"\n      }\n    ]\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Create an in-app banner shown on the next eligible session. Audience is the standard campaign `targeting` object (filterGroups / userIds)."
          }
        },
        {
          "name": "Get All In-App Campaigns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns?channel=in_app&status=active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ],
              "query": [
                {
                  "key": "channel",
                  "value": "in_app"
                },
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "page",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "disabled": true
                }
              ]
            },
            "description": "List in-app campaigns via the unified campaigns list, filtered by channel."
          }
        },
        {
          "name": "Get In-App Campaign by ID",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Update In-App Campaign",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"active\",\n  \"channelConfig\": {\n    \"type\": \"modal\",\n    \"triggers\": [\n      {\n        \"type\": \"event\",\n        \"event\": \"Order Completed\"\n      }\n    ],\n    \"priority\": 8\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "Partial update via the unified campaigns API. In-app-specific settings (priority, triggers, frequency cap) are updated inside `channelConfig`."
          }
        },
        {
          "name": "Get In-App Campaign Stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/campaigns/:campaignId/in-app-stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                ":campaignId",
                "in-app-stats"
              ],
              "variable": [
                {
                  "key": "campaignId",
                  "value": ""
                }
              ]
            },
            "description": "In-app impression/click/conversion stats for one campaign."
          }
        }
      ],
      "description": "In-app messages are standard campaigns with `channel: \"in_app\"`, managed through the unified /campaigns API. In-app-specific settings (message type, triggers, frequency cap, priority) live in `channelConfig`; content comes from `templateId` or `channelConfig` rather than `variants`."
    },
    {
      "name": "Push Notifications",
      "item": [
        {
          "name": "Register Device Token",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"ios\",\n  \"pushToken\": \"device_token_from_apns_or_fcm\",\n  \"deviceInfo\": {\n    \"appVersion\": \"1.0.0\",\n    \"osVersion\": \"17.2\",\n    \"model\": \"iPhone 15 Pro\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/users/:userId/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":userId",
                "devices"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c",
                  "description": "Joryio's internal user ID (24-char hex)"
                }
              ]
            },
            "description": "Register a device push token for a user. platform: ios|android|web; `deviceInfo` is a free-form object."
          }
        },
        {
          "name": "List Device Tokens",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/:userId/devices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":userId",
                "devices"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                }
              ]
            },
            "description": "List a user's registered devices."
          }
        },
        {
          "name": "Remove Device Token",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/users/:userId/devices/:deviceId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "users",
                ":userId",
                "devices",
                ":deviceId"
              ],
              "variable": [
                {
                  "key": "userId",
                  "value": "665f1e2a9b3c4d5e6f7a8b9c"
                },
                {
                  "key": "deviceId",
                  "value": ""
                }
              ]
            },
            "description": "Remove a registered device (e.g. when the user logs out)."
          }
        },
        {
          "name": "Send Transactional Push",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"userId\": \"user-123\",\n  \"channel\": \"push\",\n  \"message\": {\n    \"subject\": \"Your order has shipped!\",\n    \"body\": \"Track your package in real-time\"\n  },\n  \"idempotencyKey\": \"order-ORD-2024-001-shipped\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns/transactional/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns",
                "transactional",
                "send"
              ]
            },
            "description": "One-off push to a single user via the transactional send endpoint. For push, `message.subject` becomes the notification title and `message.body` the notification body. Requires a configured push app and a registered device token for the user."
          }
        },
        {
          "name": "Create Push Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Feature Announcement\",\n  \"channel\": \"push\",\n  \"sendType\": \"immediate\",\n  \"variants\": [\n    {\n      \"id\": \"variant-a\",\n      \"name\": \"Variant A\",\n      \"weight\": 100,\n      \"message\": {\n        \"title\": \"Exclusive Premium Feature\",\n        \"body\": \"Check out our new feature just for you!\"\n      }\n    }\n  ],\n  \"targeting\": {\n    \"filterGroups\": [\n      {\n        \"filters\": [\n          {\n            \"type\": \"attribute\",\n            \"field\": \"plan\",\n            \"operator\": \"equals\",\n            \"value\": \"premium\"\n          }\n        ],\n        \"operator\": \"AND\"\n      }\n    ]\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/campaigns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Send push to an audience via a standard campaign with channel push. Variant message: { title, body, icon?, image?, data? }."
          }
        }
      ],
      "description": "Push delivery runs through the standard campaign pipeline: create a campaign with `channel: \"push\"` (or use POST /campaigns/transactional/send with channel push) after registering device tokens against a user. Push provider (APNS/FCM) credentials are configured per app in the dashboard (Settings > Apps), not via this API."
    },
    {
      "name": "API Keys",
      "item": [
        {
          "name": "Create API Key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Production API Key\",\n  \"description\": \"Key for production environment\",\n  \"permissions\": [\"users:read\", \"users:write\", \"events:track\"]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/api-keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api-keys"
              ]
            }
          }
        },
        {
          "name": "List API Keys",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api-keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api-keys"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Subscriptions",
      "description": "Per-contact consent state: channel opt-in/out, list membership, bounce clearing, and the audit history. Contact-scoped (userId = internal contact id). Reads require compliance:read, writes compliance:write.",
      "item": [
        {
          "name": "Get Contact Subscriptions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId"
              ]
            },
            "description": "Return the full subscription state (all channels + lists) for a contact."
          }
        },
        {
          "name": "Update Channel Subscription",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"email\",\n  \"status\": \"unsubscribed\",\n  \"source\": \"api\",\n  \"consentText\": \"User unsubscribed via preference center\",\n  \"reason\": \"manual update\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/channels/:channel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "channels",
                ":channel"
              ]
            },
            "description": "Set a contact's status on one channel. channel path param must be one of email|sms|whatsapp|push and overrides the body channel. status is optedIn|subscribed|unsubscribed."
          }
        },
        {
          "name": "Clear Bounce Status",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"verified new mailbox\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/clear-bounce",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "clear-bounce"
              ]
            },
            "description": "Clear a soft/hard bounce flag on a contact so email sending can resume. reason is recorded for audit."
          }
        },
        {
          "name": "Get List Subscriptions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "lists"
              ]
            },
            "description": "Return the list/group memberships for a contact."
          }
        },
        {
          "name": "Subscribe Contact to List",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"email\",\n  \"source\": \"api\",\n  \"consentText\": \"Signed up via website form\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/lists/:listId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "lists",
                ":listId"
              ]
            },
            "description": "Add a contact to a list on a given channel. channel is required; source/consentText optional (defaults source=api)."
          }
        },
        {
          "name": "Unsubscribe Contact from List",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"email\",\n  \"source\": \"api\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/lists/:listId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "lists",
                ":listId"
              ]
            },
            "description": "Remove a contact from a list. Body optional; channel defaults to email."
          }
        },
        {
          "name": "Get Subscription History",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/history?limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "history"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "Paginated audit log of subscription changes for a contact. Non-numeric limit/offset fall back to defaults (50/0)."
          }
        },
        {
          "name": "Check Channel Subscription Status",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/channels/:channel/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "channels",
                ":channel",
                "status"
              ]
            },
            "description": "Returns { subscribed: boolean } for the contact on one channel (email|sms|whatsapp|push)."
          }
        },
        {
          "name": "Check Email Valid",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/subscriptions/contacts/:userId/email-valid",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "subscriptions",
                "contacts",
                ":userId",
                "email-valid"
              ]
            },
            "description": "Returns { valid: boolean } indicating whether the contact's email is deliverable (not bounced)."
          }
        }
      ]
    },
    {
      "name": "Suppressions",
      "description": "Address/number-keyed suppression ledger (channel_suppressions), scoped per workspace. On add/import, reason is clamped to manual|unsubscribe — only POST /suppressions/hard-bounce may assert a hard bounce. Suppression follows the email address or phone number itself (not a specific contact) across the workspace, and hard_bounce/complaint entries survive re-subscribe. API keys need the channel-precise scope: email_suppression:* for email, sms_suppression:* for sms/whatsapp (read for GETs, write for POST/DELETE).",
      "item": [
        {
          "name": "List Suppressions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppressions?channel=email&reason=&limit=100&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions"
              ],
              "query": [
                {
                  "key": "channel",
                  "value": "email"
                },
                {
                  "key": "reason",
                  "value": ""
                },
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "List suppressed identifiers (paginated). Optional filters: channel (email|sms|whatsapp), reason (unsubscribe|hard_bounce|complaint|manual). limit 1-1000 (default 100), offset >= 0."
          }
        },
        {
          "name": "Check Suppression",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppressions/:identifier?channel=email",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions",
                ":identifier"
              ],
              "query": [
                {
                  "key": "channel",
                  "value": "email"
                }
              ]
            },
            "description": "Check whether a single identifier (email/phone) is suppressed on a channel. channel defaults to email."
          }
        },
        {
          "name": "Add Suppression",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"email\",\n  \"identifier\": \"user@example.com\",\n  \"reason\": \"manual\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/suppressions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions"
              ]
            },
            "description": "Add a manual/consent suppression. reason is clamped to manual|unsubscribe by the service — this path can NEVER assert a hard bounce. scope global|group; listId required when scope=group."
          }
        },
        {
          "name": "Add Hard Bounce",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"identifier\": \"dead@example.com\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/suppressions/hard-bounce",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions",
                "hard-bounce"
              ]
            },
            "description": "The ONLY API path that asserts a hard bounce. Email only; recorded with source=api. A hard_bounce entry survives re-subscribe."
          }
        },
        {
          "name": "Import Suppressions",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"email\",\n  \"entries\": [\n    {\n      \"identifier\": \"a@example.com\"\n    },\n    {\n      \"identifier\": \"b@example.com\",\n      \"reason\": \"unsubscribe\"\n    }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/suppressions/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions",
                "import"
              ]
            },
            "description": "Bulk import (ESP migration). Up to 5000 entries. Per-entry reason optional (manual|unsubscribe); no entry may assert a hard bounce."
          }
        },
        {
          "name": "Remove Suppression (Unsuppress)",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppressions/:identifier?channel=email",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppressions",
                ":identifier"
              ],
              "query": [
                {
                  "key": "channel",
                  "value": "email"
                }
              ]
            },
            "description": "Remove ALL suppression rows for an identifier on a channel. channel defaults to email."
          }
        }
      ]
    },
    {
      "name": "AI Agents",
      "description": "Generate-only AI agents (ai_agents table): reusable objects that READ a bounded, opt-in context and EMIT validated structured output for journeys and catalog jobs to act on. An agent has NO tools/actions — it never sends, branches, or writes on its own. modelMode managed (hosted Claude, 1 credit/run) or byo (your provider key, small flat fee/run). Every run resolves to one outcome: success|fallback|timeout|rate_limited|invalid_config|budget_exceeded. API keys need ai_agents:read for GETs and ai_agents:write for writes; dashboard sessions may use settings:read/settings:write.",
      "item": [
        {
          "name": "Create Agent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Cart subject-line writer\",\n  \"instructions\": \"Write a short, upbeat email subject line for the abandoned cart. Max 60 characters.\",\n  \"modelMode\": \"managed\",\n  \"contextSelectors\": {\n    \"attributeKeys\": [\n      \"first_name\",\n      \"cart_total\"\n    ],\n    \"includeBrandVoice\": true\n  },\n  \"outputSchema\": {\n    \"type\": \"json\",\n    \"fields\": [\n      {\n        \"name\": \"subject\",\n        \"type\": \"string\"\n      }\n    ],\n    \"includeExplanation\": true\n  },\n  \"fallbackValue\": {\n    \"subject\": \"You left something behind\"\n  },\n  \"dailyCap\": 50000,\n  \"guardrails\": {\n    \"timeoutMs\": 20000,\n    \"retryOnTransient\": true\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents"
              ]
            },
            "description": "Create a generate-only AI agent. Required: name, instructions. modelMode managed|byo (default managed); provider joryio|anthropic|openai|google|azure|bedrock. outputSchema type string|number|boolean|json (json carries a fields[] list). contextSelectors is opt-in. dailyCap default 250000. Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "List Agents",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents?status=active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active"
                }
              ]
            },
            "description": "List the workspace's AI agents (newest-updated first). Optional status filter: active|archived. Needs ai_agents:read (or settings:read)."
          }
        },
        {
          "name": "List Provider Keys",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/provider-keys",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "provider-keys"
              ]
            },
            "description": "List registered BYO provider keys for the workspace. Credential values are NEVER returned (credentials is always {}). Needs ai_agents:read (or settings:read)."
          }
        },
        {
          "name": "Upsert Provider Key",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credentials\": {\n    \"apiKey\": \"sk-your-openai-key\"\n  },\n  \"label\": \"Production OpenAI\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents/provider-keys/:provider",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "provider-keys",
                ":provider"
              ]
            },
            "description": "Create or replace the BYO key for one provider (path :provider = anthropic|openai|google|azure|bedrock; 'joryio' is rejected). credentials is provider-specific ({ apiKey } etc), encrypted at rest and never returned. One key per (workspace, provider). Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Delete Provider Key",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/provider-keys/:provider",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "provider-keys",
                ":provider"
              ]
            },
            "description": "Delete the BYO key for the given provider (path :provider = anthropic|openai|google|azure|bedrock). Returns { success: true }. Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Get Agent",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id"
              ]
            },
            "description": "Fetch one AI agent by id (workspace-scoped; 404 otherwise). Needs ai_agents:read (or settings:read)."
          }
        },
        {
          "name": "Update Agent",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"dailyCap\": 100000,\n  \"guardrails\": {\n    \"timeoutMs\": 15000,\n    \"retryOnTransient\": false\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id"
              ]
            },
            "description": "Partial update of an agent (send only changed fields). Accepts all create fields plus status (active|archived). Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Archive Agent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": ""
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id",
                "archive"
              ]
            },
            "description": "Soft-archive an agent: status becomes archived (halts use, keeps run history). Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Delete Agent",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id"
              ]
            },
            "description": "Delete an agent. Returns { success: true }. Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Test Agent (Preview)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributes\": {\n    \"first_name\": \"Dana\",\n    \"cart_total\": 249.9\n  },\n  \"segmentMemberships\": [\n    \"vip\"\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id/test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id",
                "test"
              ]
            },
            "description": "Dry-run the agent against a SAMPLE context (attributes, segmentMemberships, catalogRecord, engagement). Uses a fresh run key and never counts against a real journey. Returns { outcome, output, explanation }. outcome is success|fallback|timeout|rate_limited|invalid_config|budget_exceeded. Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "List Agent Runs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/:id/runs?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                ":id",
                "runs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            },
            "description": "Recent run traces for one agent (newest first). limit 1-200 (default 50). Trace stores input REFERENCES + output + explanation only, never the raw prompt text. Needs ai_agents:read (or settings:read)."
          }
        },
        {
          "name": "Run Catalog Enrichment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"agentId\": \"8f0e2b3a-1c4d-4e5f-9a0b-1c2d3e4f5a6b\",\n  \"entityDefinitionId\": \"product\",\n  \"targetField\": \"ai_description\",\n  \"filter\": {\n    \"category\": \"shoes\"\n  },\n  \"limit\": 200\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/ai-agents/enrichment/run",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "enrichment",
                "run"
              ]
            },
            "description": "Run a generate-only agent over a custom-entity's records and write each output into targetField. Required: agentId (active agent), entityDefinitionId, targetField. Optional filter (Mongo-style) and limit (up to 100k, hard-capped). ASYNC/QUEUED: this ENQUEUES a job and returns immediately with the queued job — a large catalog processes off the request path. Idempotent per record (runKey = jobId:recordId), so a retried job never re-charges an already-enriched record. Returns { jobId, status: 'queued', counts: { total, processed, succeeded, failed, skipped }, ... }; poll 'Get Enrichment Job' for progress. Needs ai_agents:write (or settings:write)."
          }
        },
        {
          "name": "Get Enrichment Job",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/enrichment/jobs/:jobId",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "enrichment",
                "jobs",
                ":jobId"
              ],
              "variable": [
                {
                  "key": "jobId",
                  "value": "job_9a8b7c",
                  "description": "The enrichment job id returned by Run Catalog Enrichment."
                }
              ]
            },
            "description": "Poll one enrichment job's status + counts. Returns { jobId, status: 'queued'|'running'|'completed'|'failed', counts: { total, processed, succeeded, failed, skipped }, error?, ... }. 404 if the job is not in this workspace. Needs ai_agents:read (or settings:read)."
          }
        },
        {
          "name": "List Enrichment Jobs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/ai-agents/enrichment/jobs?limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "ai-agents",
                "enrichment",
                "jobs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "List recent enrichment jobs (newest first) for progress/history. Optional limit (default 20). Each row is the same shape as Get Enrichment Job. Needs ai_agents:read (or settings:read)."
          }
        }
      ]
    },
    {
      "name": "Attributes",
      "description": "Custom contact-attribute dictionary (data dictionary). GET is open to any authenticated caller (used by pickers); create/update/delete require users:write.",
      "item": [
        {
          "name": "List Attributes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attributes?q=&limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attributes"
              ],
              "query": [
                {
                  "key": "q",
                  "value": ""
                },
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "List custom attribute definitions. With no params returns the full pool; passing q/limit/offset returns a paginated envelope."
          }
        },
        {
          "name": "Create Attribute",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"loyalty_tier\",\n  \"type\": \"string\",\n  \"description\": \"Customer loyalty tier\",\n  \"defaultValue\": \"bronze\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attributes"
              ]
            },
            "description": "Define a new custom contact attribute. name (<=255) and type (<=50) required; description/defaultValue optional."
          }
        },
        {
          "name": "Update Attribute",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"description\": \"Updated description\",\n  \"type\": \"string\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/attributes/:name",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attributes",
                ":name"
              ]
            },
            "description": "Update an attribute's description/type by name."
          }
        },
        {
          "name": "Delete Attribute",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attributes/:name",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attributes",
                ":name"
              ]
            },
            "description": "Delete a custom attribute definition by name."
          }
        }
      ]
    },
    {
      "name": "Analytics",
      "description": "Read/reporting API over the event warehouse: event explorer, funnels, retention, path analysis, cohorts, and realtime. All routes require analytics:read.",
      "item": [
        {
          "name": "List Events",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/analytics/events?limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "events"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "List raw events with filtering and pagination (event explorer)."
          }
        },
        {
          "name": "Query Events",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eventName\": \"Order Completed\",\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/events/query",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "events",
                "query"
              ]
            },
            "description": "Run a custom aggregated event query."
          }
        },
        {
          "name": "Event Trend",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eventName\": \"Order Completed\",\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\",\n  \"timeGranularity\": \"day\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/events/trend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "events",
                "trend"
              ]
            },
            "description": "Get an event count trend over time. timeGranularity: hour|day|week|month."
          }
        },
        {
          "name": "Get Event Properties",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/analytics/events/:eventName/properties?startDate=2026-01-01&endDate=2026-01-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "events",
                ":eventName",
                "properties"
              ],
              "query": [
                {
                  "key": "startDate",
                  "value": "2026-01-01"
                },
                {
                  "key": "endDate",
                  "value": "2026-01-31"
                }
              ]
            },
            "description": "List the property keys seen on a given event type over a date range."
          }
        },
        {
          "name": "Property Breakdown",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"eventName\": \"Order Completed\",\n  \"property\": \"category\",\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/events/property-breakdown",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "events",
                "property-breakdown"
              ]
            },
            "description": "Break down an event by the distinct values of one property."
          }
        },
        {
          "name": "List Funnels",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/analytics/funnels",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "funnels"
              ]
            },
            "description": "List saved funnel definitions."
          }
        },
        {
          "name": "Create Funnel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Purchase funnel\",\n  \"steps\": [\n    {\n      \"id\": \"step-1\",\n      \"order\": 0,\n      \"eventName\": \"Product Viewed\"\n    },\n    {\n      \"id\": \"step-2\",\n      \"order\": 1,\n      \"eventName\": \"Product Added\"\n    },\n    {\n      \"id\": \"step-3\",\n      \"order\": 2,\n      \"eventName\": \"Order Completed\"\n    }\n  ],\n  \"conversionWindowDays\": 7\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/funnels",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "funnels"
              ]
            },
            "description": "Create a saved funnel definition. Each step requires id, order (0-based), and eventName; optional label and property filters."
          }
        },
        {
          "name": "Quick Funnel Analysis",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"steps\": [\n    {\n      \"id\": \"step-1\",\n      \"order\": 0,\n      \"eventName\": \"Product Viewed\"\n    },\n    {\n      \"id\": \"step-2\",\n      \"order\": 1,\n      \"eventName\": \"Order Completed\"\n    }\n  ],\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/funnels/quick-analysis",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "funnels",
                "quick-analysis"
              ]
            },
            "description": "Run an ad-hoc funnel analysis without saving it (rate-limited)."
          }
        },
        {
          "name": "Retention Analysis",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"startEvent\": \"signup_completed\",\n  \"returnEvent\": \"Order Completed\",\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\",\n  \"timeUnit\": \"week\",\n  \"periods\": 8\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/retention/analyze",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "retention",
                "analyze"
              ]
            },
            "description": "Compute a retention/cohort-retention grid. timeUnit: day|week|month; periods 1-30."
          }
        },
        {
          "name": "Path Analysis",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"startDate\": \"2026-01-01\",\n  \"endDate\": \"2026-01-31\",\n  \"endEvent\": \"Order Completed\",\n  \"direction\": \"backward\",\n  \"maxSteps\": 5,\n  \"minPathCount\": 10,\n  \"minPathPercent\": 0.5,\n  \"maxBranchesPerStep\": 5\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/analytics/paths/analyze",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "paths",
                "analyze"
              ]
            },
            "description": "Analyze common user paths. Anchor on startEvent with direction \"forward\" (what users do next) or endEvent with direction \"backward\" (how users got there). Optional maxSteps (2-10), includeEvents/excludeEvents. Frequency threshold (a path/step must clear the LARGER of the two): minPathCount (absolute users, default 1) and minPathPercent (percent of qualifying users 0-100, portable across traffic levels). maxBranchesPerStep (1-50, default 8) caps the flow diagram to the top-N next events per step so it stays readable at any volume."
          }
        },
        {
          "name": "List Cohorts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/analytics/cohorts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "cohorts"
              ]
            },
            "description": "List saved behavioral cohorts."
          }
        },
        {
          "name": "Realtime Active Users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/analytics/realtime/active-users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "analytics",
                "realtime",
                "active-users"
              ]
            },
            "description": "Current count of active users in the live window."
          }
        }
      ]
    },
    {
      "name": "Wallet Passes",
      "item": [
        {
          "name": "Record Pass Scan / Redemption",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"serialNumber\": \"a1b2c3d4-e5f6-4789-abcd-1234567890ab\",\n  \"properties\": {\n    \"storeId\": \"store_42\",\n    \"amount\": 19.90,\n    \"currency\": \"EUR\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/wallet-passes/scan",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "wallet-passes",
                "scan"
              ]
            },
            "description": "Record a wallet pass scan/redemption at the point of sale (POS / merchant integration). `serialNumber` is the pass's unique id, read from its barcode/QR. `properties` is an optional free-form object of redemption metadata (store, amount, cashier...) stored on the resulting `pass.scanned` event and available in Wallet analytics. Requires the `wallet:write` permission. Returns `{ \"recorded\": true }`, or `{ \"recorded\": false }` if no pass with that serial exists in your organization. Counting is idempotent per pass: re-scanning the same serial still counts as one distinct scanned pass."
          }
        }
      ]
    },
    {
      "name": "Messages",
      "description": "Read API for a contact's INBOUND messages (replies) across channels. Requires users:read; userId is the internal contact id.",
      "item": [
        {
          "name": "List Inbound Messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages/inbound?userId=:userId&channel=&limit=50&offset=0",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages",
                "inbound"
              ],
              "query": [
                {
                  "key": "userId",
                  "value": ":userId"
                },
                {
                  "key": "channel",
                  "value": ""
                },
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "offset",
                  "value": "0"
                }
              ]
            },
            "description": "List a contact's inbound messages across all channels (newest-first). userId required; optional channel filter (e.g. sms, whatsapp). limit default 50 (1-200)."
          }
        },
        {
          "name": "List Inbound SMS",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sms/messages?userId=:userId&limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sms",
                "messages"
              ],
              "query": [
                {
                  "key": "userId",
                  "value": ":userId"
                },
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            },
            "description": "List a contact's inbound SMS messages (profile timeline, newest-first). userId required."
          }
        }
      ]
    }
  ]
}
