{
  "info": {
    "_postman_id": "wave-api-v2",
    "name": "WAVE API v2",
    "description": "Complete API collection for WAVE — Workflow & Analytics for Value Drivent Engagement.\n\n**Setup:**\n1. Set the `base_url` variable to `https://wavebitz.com/api`\n2. Set `api_key` to your tenant API key (`wv_...`)\n3. Set `tenant_slug` to your tenant slug\n\nRun **Customer API / Authenticate** first — it auto-saves the bearer token to `bearer_token`.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://wavebitz.com/api",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "wv_your_key_here",
      "type": "string"
    },
    {
      "key": "bearer_token",
      "value": "",
      "type": "string"
    },
    {
      "key": "tenant_slug",
      "value": "your-tenant-slug",
      "type": "string"
    },
    {
      "key": "customer_id",
      "value": "usr_001",
      "type": "string"
    },
    {
      "key": "segment_id",
      "value": "1",
      "type": "string"
    },
    {
      "key": "campaign_id",
      "value": "1",
      "type": "string"
    },
    {
      "key": "message_id",
      "value": "1",
      "type": "string"
    },
    {
      "key": "card_id",
      "value": "1",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Platform API",
      "description": "Backend → Wave. Authenticate with `X-Api-Key`. Use from your server, never from client-side code.\n\n**Rate limits (per tenant, per minute):**\n- `POST /v1/events` — 300 req/min (default)\n- `POST /v1/customers/identify` — 60 req/min (default)\n- `POST /v1/devices/register` — 30 req/min (default)\n- `POST /v1/notifications/send` — 60 req/min (default)\n\nLimits may vary by plan. Exceeded limits return `429` with a `Retry-After` header. Event and customer volume quotas are soft — they trigger warnings but never block requests.",
      "item": [
        {
          "name": "Track Event (single)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/events",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "events"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\":     \"{{customer_id}}\",\n  \"event_name\":      \"transfer_completed\",\n  \"occurred_at\":     \"2026-07-01T03:00:00Z\",\n  \"idempotency_key\": \"trx-7f3a9c2d\",\n  \"properties\": {\n    \"amount\":           2500000,\n    \"destination_bank\": \"BCA\",\n    \"currency\":         \"IDR\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Ingest a single event. `occurred_at` must be UTC. Idempotency key prevents duplicate ingestion on retry — generate it before the first attempt and reuse it on every retry.\n\n**Required:** `customer_id`, `event_name`\n**Optional:** `properties`, `occurred_at`, `idempotency_key`"
          },
          "response": [
            {
              "name": "201 Created",
              "status": "Created",
              "code": 201,
              "body": "{\n  \"success\":  true,\n  \"event_id\": \"018f3a9c-2d4e-7a1b-b3c1-2d9e0f1a5b6c\"\n}"
            },
            {
              "name": "200 Deduplicated",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"success\":       true,\n  \"deduplicated\":  true\n}"
            }
          ]
        },
        {
          "name": "Track Events (batch)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/events/batch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "events",
                "batch"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"customer_id\":     \"{{customer_id}}\",\n      \"event_name\":      \"page_viewed\",\n      \"occurred_at\":     \"2026-07-01T03:00:00Z\",\n      \"idempotency_key\": \"page-home-001\",\n      \"properties\": { \"page\": \"home\" }\n    },\n    {\n      \"customer_id\":     \"{{customer_id}}\",\n      \"event_name\":      \"item_added_to_cart\",\n      \"occurred_at\":     \"2026-07-01T03:00:05Z\",\n      \"idempotency_key\": \"cart-sku42-001\",\n      \"properties\": { \"sku\": \"SKU-42\", \"price\": 250000 }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Ingest up to 100 events in a single request. Each event follows the same schema as `Track Event (single)`. Idempotency keys are strongly recommended to prevent duplicates on retry.\n\nResponse includes per-event status: `accepted` or `deduplicated`."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"accepted\":     2,\n  \"deduplicated\": 0,\n  \"results\": [\n    { \"status\": \"accepted\",     \"event_id\": \"018f3a9c-2d4e-7a1b-b3c1-2d9e0f1a5b6c\" },\n    { \"status\": \"accepted\",     \"event_id\": \"018f3a9c-2d4e-7a1b-b3c1-2d9e0f1a5b6d\" }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Identify Customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/customers/identify",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "customers",
                "identify"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"attributes\": {\n    \"name\":       \"Budi Santoso\",\n    \"email\":      \"budi@example.com\",\n    \"tier\":       \"premium\",\n    \"city\":       \"Jakarta\",\n    \"birth_date\": \"1990-04-15\",\n    \"balance\":    12500000\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Create or update a customer profile. Attributes are **merged** — existing keys not present in this call are preserved. Safe to call on every app session.\n\n`birth_date` format: `YYYY-MM-DD` — used by the Birthday Scheduled Event."
          }
        },
        {
          "name": "Batch Identify Customers",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/customers/identify/batch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "customers",
                "identify",
                "batch"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customers\": [\n    {\n      \"customer_id\": \"C001\",\n      \"attributes\": {\n        \"name\":  \"Budi Santoso\",\n        \"email\": \"budi@example.com\",\n        \"tier\":  \"premium\"\n      }\n    },\n    {\n      \"customer_id\": \"C002\",\n      \"attributes\": {\n        \"name\":  \"Siti Rahayu\",\n        \"email\": \"siti@example.com\",\n        \"tier\":  \"standard\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Create or update up to 1,000 customer profiles in a single request. Each entry follows the same merge semantics as the single `POST /v1/customers/identify` call — existing attributes not present in the payload are preserved.\n\nRate limit: same `identify` bucket (60 req/min default). Each batch call counts as one request regardless of how many customers it contains.\n\nReturns a `results` array in the same order as the input, with per-customer `success` and any attribute `warnings`."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"count\": 2,\n  \"results\": [\n    { \"customer_id\": \"C001\", \"success\": true },\n    { \"customer_id\": \"C002\", \"success\": true }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Register Device",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/devices/register",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "devices",
                "register"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"token\":       \"fcm-token-here\",\n  \"platform\":    \"android\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Link an FCM push token to a customer. Call this:\n- On app launch (after the user is authenticated)\n- Whenever `FirebaseMessaging.instance.onTokenRefresh` fires\n\n`platform`: `android` | `ios`"
          }
        },
        {
          "name": "Send Push Notification (direct)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/notifications/send",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "notifications",
                "send"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"title\":       \"Transfer berhasil\",\n  \"body\":        \"Transfer Rp 2.500.000 ke BCA telah selesai.\",\n  \"image_url\":   null,\n  \"data\": {\n    \"type\":   \"transfer\",\n    \"ref_id\": \"TRX-2026-00182736\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Send a push notification directly to all active devices of a customer, bypassing journeys. Use this for transactional notifications you control programmatically.\n\n`data` values must all be strings. `image_url` is optional.\n\nWave deactivates any FCM tokens that FCM rejects during delivery."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"success\":         true,\n  \"notification_id\": 42,\n  \"tokens_count\":    2,\n  \"success_count\":   2,\n  \"failure_count\":   0\n}"
            },
            {
              "name": "422 No active tokens",
              "status": "Unprocessable Entity",
              "code": 422,
              "body": "{\n  \"success\":         false,\n  \"notification_id\": 43,\n  \"message\":         \"No active device tokens for this customer\",\n  \"tokens_count\":    0\n}"
            }
          ]
        },
        {
          "name": "List Segments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/segments",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "segments"
              ]
            },
            "description": "Return all segments for the tenant. Includes current `customer_count` and `last_evaluated_at`."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"data\": [\n    {\n      \"id\":                 1,\n      \"name\":               \"High-Value Active\",\n      \"description\":        \"Spend > 1M in 30d + active last 7d\",\n      \"customer_count\":     124,\n      \"last_evaluated_at\":  \"2026-07-01T03:00:00Z\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Evaluate Segment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/segments/{{segment_id}}/evaluate",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "segments",
                "{{segment_id}}",
                "evaluate"
              ]
            },
            "description": "Re-evaluate segment membership immediately and return the current list of matching customer IDs. Also updates `customer_count` and `last_evaluated_at` on the segment."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"segment_id\":   1,\n  \"segment_name\": \"High-Value Active\",\n  \"count\":        124,\n  \"customer_ids\": [\"usr_001\", \"usr_007\", \"usr_042\"]\n}"
            }
          ]
        },
        {
          "name": "Create Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/campaigns",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "campaigns"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\":                   \"Promo Akhir Bulan Juli\",\n  \"target_type\":            \"segment\",\n  \"segment_id\":             1,\n  \"notification_title\":     \"Promo khusus untukmu!\",\n  \"notification_body\":      \"Nikmati cashback 10% untuk setiap transfer hari ini.\",\n  \"notification_image_url\": null,\n  \"notification_data\": {\n    \"type\":       \"promo\",\n    \"promo_code\": \"JULI10\"\n  },\n  \"scheduled_at\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Create a campaign in `draft` status. It will not send until you call **Launch Campaign**.\n\n`target_type`: `segment` (requires `segment_id`) | `all` (all customers)\n`scheduled_at`: optional future UTC datetime — if set, launch queues it for that time instead of sending immediately.\n`notification_data` values must all be strings."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) {",
                  "  pm.collectionVariables.set('campaign_id', res.data.id);",
                  "  console.log('campaign_id set to', res.data.id);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ]
        },
        {
          "name": "Get Campaign",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/campaigns/{{campaign_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}"
              ]
            },
            "description": "Fetch a campaign by ID, including its segment name and current status."
          }
        },
        {
          "name": "Launch Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/campaigns/{{campaign_id}}/launch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "campaigns",
                "{{campaign_id}}",
                "launch"
              ]
            },
            "description": "Launch a campaign from `draft` or `failed` status.\n\n- If `scheduled_at` is in the future → status becomes `scheduled`\n- Otherwise → queued immediately, status becomes `running`\n\nCannot re-launch a campaign already in `running`, `sent`, or `scheduled` status."
          },
          "response": [
            {
              "name": "200 Queued",
              "status": "OK",
              "code": 200,
              "body": "{ \"message\": \"Campaign queued for sending\", \"status\": \"running\" }"
            },
            {
              "name": "200 Scheduled",
              "status": "OK",
              "code": 200,
              "body": "{ \"message\": \"Campaign scheduled\", \"status\": \"scheduled\" }"
            },
            {
              "name": "422 Invalid status",
              "status": "Unprocessable Entity",
              "code": 422,
              "body": "{ \"message\": \"Campaign cannot be launched in its current status\" }"
            }
          ]
        },
        {
          "name": "Read API",
          "description": "Tenant-scoped read endpoints. X-Api-Key required. 120 req/min rate limit. Server-side only — never expose the API key in a browser or mobile app.",
          "item": [
            {
              "name": "Get Customer Intelligence Fields",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/intelligence/fields",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "intelligence",
                    "fields"
                  ],
                  "variable": []
                },
                "description": "Return all tenant-defined Calculated Fields for a customer. Fields not yet computed are included with status: no_data."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"data\": [\n    {\n      \"field\":       \"loyalty_tier\",\n      \"label\":       \"Loyalty Tier\",\n      \"value\":       \"gold\",\n      \"status\":      \"available\",\n      \"computed_at\": \"2026-10-20T05:00:00.000000Z\"\n    },\n    {\n      \"field\":       \"risk_segment\",\n      \"label\":       \"Risk Segment\",\n      \"value\":       null,\n      \"status\":      \"no_data\",\n      \"computed_at\": null\n    }\n  ]\n}"
                }
              ]
            },
            {
              "name": "Get Customer Intelligence Field (single)",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/intelligence/fields/loyalty_tier",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "intelligence",
                    "fields",
                    "loyalty_tier"
                  ],
                  "variable": []
                },
                "description": "Return a single Calculated Field by slug. Returns 404 if the slug does not exist for this tenant."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"field\":       \"loyalty_tier\",\n  \"label\":       \"Loyalty Tier\",\n  \"value\":       \"gold\",\n  \"status\":      \"available\",\n  \"computed_at\": \"2026-10-20T05:00:00.000000Z\"\n}"
                }
              ]
            },
            {
              "name": "Get Customer Intelligence Metrics",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/intelligence/metrics",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "intelligence",
                    "metrics"
                  ],
                  "variable": []
                },
                "description": "Return all tenant-defined CI Metrics for a customer. The -1.0 sentinel is normalized to null with status: no_data."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"data\": [\n    {\n      \"metric\":      \"total_orders\",\n      \"label\":       \"Total Orders\",\n      \"value\":       42.0,\n      \"status\":      \"available\",\n      \"computed_at\": \"2026-10-20T05:00:00.000000Z\"\n    },\n    {\n      \"metric\":      \"days_since_last_purchase\",\n      \"label\":       \"Days Since Last Purchase\",\n      \"value\":       null,\n      \"status\":      \"no_data\",\n      \"computed_at\": null\n    }\n  ]\n}"
                }
              ]
            },
            {
              "name": "Get Customer Intelligence Metric (single)",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/intelligence/metrics/total_orders",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "intelligence",
                    "metrics",
                    "total_orders"
                  ],
                  "variable": []
                },
                "description": "Return a single CI Metric by slug. Returns 404 if the slug does not exist for this tenant."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"metric\":      \"total_orders\",\n  \"label\":       \"Total Orders\",\n  \"value\":       42.0,\n  \"status\":      \"available\",\n  \"computed_at\": \"2026-10-20T05:00:00.000000Z\"\n}"
                }
              ]
            },
            {
              "name": "Get Customer Segments",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/segments",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "segments"
                  ],
                  "variable": []
                },
                "description": "Return all segments the customer currently belongs to. Evaluated live. Empty array means no matches — not an error."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"data\": [\n    { \"id\": 4, \"slug\": \"high-value\",   \"name\": \"High-Value Customers\" },\n    { \"id\": 7, \"slug\": \"reactivation\", \"name\": \"Reactivation Candidates\" }\n  ]\n}"
                }
              ]
            },
            {
              "name": "Get Campaign Delivery",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/campaigns/{{campaign_id}}/delivery",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "campaigns",
                    "{{campaign_id}}",
                    "delivery"
                  ],
                  "variable": []
                },
                "description": "Return latest delivery attempt per channel for a customer on a specific campaign. Retries are coalesced (only is_latest=true rows returned). Empty array = not yet delivered."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"campaign_id\": 1,\n  \"data\": [\n    {\n      \"channel\":         \"push\",\n      \"status\":          \"delivered\",\n      \"sent_at\":         \"2026-10-20T05:00:00.000000Z\",\n      \"delivered_at\":    \"2026-10-20T05:00:04.000000Z\",\n      \"first_opened_at\": \"2026-10-20T05:03:11.000000Z\",\n      \"failure_reason\":  null\n    }\n  ]\n}"
                }
              ]
            },
            {
              "name": "Get Customer Executions",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/customers/{{customer_id}}/executions?limit=20",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "customers",
                    "{{customer_id}}",
                    "executions"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "20",
                      "description": "1–100 (default 50)"
                    }
                  ],
                  "variable": []
                },
                "description": "Return journey execution history for a customer. Ordered by fire_at DESC. Use ?limit= for page size (default 50, max 100).\n\nStatus values: pending, waiting, firing, sent, failed, exited, skipped."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"data\": [\n    {\n      \"id\":           1041,\n      \"journey_id\":   3,\n      \"journey_name\": \"Reactivation — 30 days\",\n      \"status\":       \"sent\",\n      \"fire_at\":      \"2026-10-20T05:00:00.000000Z\",\n      \"sent_at\":      \"2026-10-20T05:00:01.000000Z\",\n      \"skip_reason\":  null\n    }\n  ]\n}"
                }
              ]
            },
            {
              "name": "Get Journey Deliveries",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/journeys/{{journey_id}}/deliveries?limit=50",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "journeys",
                    "{{journey_id}}",
                    "deliveries"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "50",
                      "description": "1–200 (default 50)"
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Delivery ID for cursor-based pagination. Pass next_cursor from the previous response.",
                      "disabled": true
                    }
                  ],
                  "variable": []
                },
                "description": "Return all delivery records for a journey, across all customers. Ordered by sent_at DESC.\n\nUse `?limit=` for page size (default 50, max 200). For pagination, pass the `next_cursor` value from the previous response as `?cursor=`.\n\nStatus values: `pending`, `sent`, `delivered`, `opened`, `clicked`, `failed`, `skipped`.\n\nChannel values: `push`, `email`, `whatsapp`, `in_app`."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"journey_id\": 3,\n  \"data\": [\n    {\n      \"customer_id\":      \"C001\",\n      \"channel\":          \"push\",\n      \"status\":           \"opened\",\n      \"sent_at\":          \"2026-10-20T05:00:01.000000Z\",\n      \"delivered_at\":     \"2026-10-20T05:00:03.000000Z\",\n      \"first_opened_at\":  \"2026-10-20T05:04:12.000000Z\",\n      \"first_clicked_at\": null,\n      \"failure_reason\":   null\n    }\n  ],\n  \"meta\": {\n    \"limit\":       50,\n    \"has_more\":    true,\n    \"next_cursor\": 988\n  }\n}"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name": "Customer API",
      "description": "App → Wave. Used from your mobile or web frontend on behalf of an authenticated customer.",
      "item": [
        {
          "name": "Authenticate Customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/auth",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "auth"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": \"{{customer_id}}\",\n  \"pin\":         \"123456\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Issue a 8-hour bearer token for a customer. Also ingests a `login` event automatically.\n\n**Demo PIN:** `123456` for all customers.\n\nThe test script saves the token to `{{bearer_token}}` automatically."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.token) {",
                  "  pm.collectionVariables.set('bearer_token', res.token);",
                  "  console.log('bearer_token saved');",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"token\": \"abc123def456...\",\n  \"customer\": {\n    \"id\":   \"usr_001\",\n    \"name\": \"Budi Santoso\",\n    \"tier\": \"premium\"\n  }\n}"
            },
            {
              "name": "401 Invalid credentials",
              "status": "Unauthorized",
              "code": 401,
              "body": "{ \"error\": \"Invalid credentials\" }"
            }
          ]
        },
        {
          "name": "Get My Profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{bearer_token}}"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Return the authenticated customer's profile and all attributes."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"id\":         \"usr_001\",\n  \"name\":       \"Budi Santoso\",\n  \"tier\":       \"premium\",\n  \"city\":       \"Jakarta\",\n  \"email\":      \"budi@example.com\",\n  \"attributes\": {\n    \"name\":       \"Budi Santoso\",\n    \"tier\":       \"premium\",\n    \"birth_date\": \"1990-04-15\"\n  }\n}"
            }
          ]
        },
        {
          "name": "List My Executions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{bearer_token}}"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/me/executions",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "me",
                "executions"
              ]
            },
            "description": "Return the 20 most recent journey executions for the authenticated customer. Useful for in-app notification history or debugging."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"executions\": [\n    {\n      \"id\":         101,\n      \"status\":     \"sent\",\n      \"trigger\":    \"transfer_completed\",\n      \"properties\": { \"amount\": 2500000 },\n      \"journey\":    \"High-Value Transfer Alert\",\n      \"action\":     \"notification\",\n      \"fired_at\":   \"2026-07-01T03:00:05Z\",\n      \"sent_at\":    \"2026-07-01T03:00:06Z\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "List My Notifications",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{bearer_token}}"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/me/notifications",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "me",
                "notifications"
              ]
            },
            "description": "Return the 30 most recent push notifications delivered to the authenticated customer. Only `sent` and `partial_success` notifications are included."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{\n  \"notifications\": [\n    {\n      \"id\":        42,\n      \"title\":     \"Transfer berhasil\",\n      \"body\":      \"Transfer Rp 2.500.000 ke BCA telah selesai.\",\n      \"image_url\": null,\n      \"sent_at\":   \"2026-07-01T03:00:06Z\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Track Push Opened",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Api-Key",
                "value": "{{api_key}}"
              },
              {
                "key": "X-Customer-Id",
                "value": "{{customer_id}}"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/push/opened",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "push",
                "opened"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"delivery_id\": \"42\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Record that a push notification was opened by the customer. Call this when the user taps on an FCM notification.\n\n`delivery_id` is the `wave_delivery_id` from the FCM message `data` payload."
          },
          "response": [
            {
              "name": "200 OK",
              "status": "OK",
              "code": 200,
              "body": "{ \"ok\": true }"
            }
          ]
        },
        {
          "name": "In-App Messages",
          "description": "SDK lifecycle endpoints for in-app banner/modal messages. State machine: pending → delivered → displayed → clicked | dismissed.",
          "item": [
            {
              "name": "Fetch Pending Messages",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/messages/pending",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "messages",
                    "pending"
                  ]
                },
                "description": "Fetch the next in-app banner for this customer. On each call, the oldest `pending` message is atomically claimed (`pending → delivered`). Already-`displayed` messages are also returned — banners persist until the customer clicks or dismisses them.\n\nCall on app launch or resume. Returns an empty `messages` array when nothing is queued.\n\nCrash-safe: if the SDK claimed a message but crashed before calling `/display`, the next call still returns that message."
              },
              "response": [
                {
                  "name": "200 OK — message queued",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"messages\": [\n    {\n      \"id\":         1,\n      \"title\":      \"Special offer just for you!\",\n      \"body\":       \"Get 10% cashback on your next transfer.\",\n      \"image_url\":  null,\n      \"cta\":        { \"label\": \"Claim Now\", \"url\": \"https://example.com/promo\" },\n      \"expires_at\": null\n    }\n  ]\n}"
                },
                {
                  "name": "200 OK — nothing pending",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"messages\": [] }"
                }
              ]
            },
            {
              "name": "Mark Displayed",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/messages/{{message_id}}/display",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "messages",
                    "{{message_id}}",
                    "display"
                  ]
                },
                "description": "Transitions state `delivered → displayed`. Emits a `in_app.impression` event. Call when the banner becomes visible on screen."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                },
                {
                  "name": "404 Not found",
                  "status": "Not Found",
                  "code": 404,
                  "body": "{ \"error\": \"Message not found\" }"
                },
                {
                  "name": "409 Wrong state",
                  "status": "Conflict",
                  "code": 409,
                  "body": "{ \"error\": \"Message must be in 'delivered' state\", \"status\": \"dismissed\" }"
                }
              ]
            },
            {
              "name": "Mark Clicked",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/messages/{{message_id}}/click",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "messages",
                    "{{message_id}}",
                    "click"
                  ]
                },
                "description": "Transitions state `displayed → clicked`. Emits a `in_app.clicked` event. Call when the customer taps the CTA."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                }
              ]
            },
            {
              "name": "Mark Dismissed",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/messages/{{message_id}}/dismiss",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "messages",
                    "{{message_id}}",
                    "dismiss"
                  ]
                },
                "description": "Transitions state `displayed → dismissed`. Emits a `in_app.dismissed` event. Call when the customer closes the banner."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                }
              ]
            }
          ]
        },
        {
          "name": "Content Cards",
          "description": "Persistent in-app feed. Unlike banners, multiple cards can be active simultaneously. Cards accumulate until clicked, dismissed, or expired.",
          "item": [
            {
              "name": "Fetch Content Cards",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/content-cards",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "content-cards"
                  ]
                },
                "description": "Return up to 20 active content cards for this customer, ordered by priority (lower = higher priority) then `published_at` descending. Automatically expires stale cards before responding."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{\n  \"cards\": [\n    {\n      \"id\":         1,\n      \"title\":      \"Your loyalty tier upgraded!\",\n      \"body\":       \"You're now Gold — enjoy free transfers.\",\n      \"image_url\":  null,\n      \"cta\":        { \"label\": \"View Benefits\", \"action\": \"url\", \"url\": \"https://example.com/benefits\" },\n      \"priority\":   10,\n      \"expires_at\": null\n    }\n  ]\n}"
                },
                {
                  "name": "200 OK — empty feed",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"cards\": [] }"
                }
              ]
            },
            {
              "name": "Record Impression",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/content-cards/{{card_id}}/impression",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "content-cards",
                    "{{card_id}}",
                    "impression"
                  ]
                },
                "description": "Record that the card was rendered on screen. Idempotent — `first_impression_at` is set once, `last_impression_at` is updated on every call. Safe to call every time the card scrolls into view."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                }
              ]
            },
            {
              "name": "Mark Clicked",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/content-cards/{{card_id}}/click",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "content-cards",
                    "{{card_id}}",
                    "click"
                  ]
                },
                "description": "Mark the card as clicked and remove it from the active feed (`active → clicked`). Idempotent."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                }
              ]
            },
            {
              "name": "Dismiss Card",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Api-Key",
                    "value": "{{api_key}}"
                  },
                  {
                    "key": "X-Customer-Id",
                    "value": "{{customer_id}}"
                  }
                ],
                "url": {
                  "raw": "{{base_url}}/v1/content-cards/{{card_id}}/dismiss",
                  "host": [
                    "{{base_url}}"
                  ],
                  "path": [
                    "v1",
                    "content-cards",
                    "{{card_id}}",
                    "dismiss"
                  ]
                },
                "description": "Dismiss the card and remove it from the active feed (`active → dismissed`). Idempotent."
              },
              "response": [
                {
                  "name": "200 OK",
                  "status": "OK",
                  "code": 200,
                  "body": "{ \"ok\": true }"
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Third-party system → Wave. No X-Api-Key — auth is via HMAC signature header `X-Wave-Signature`.",
      "item": [
        {
          "name": "Inbound Webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Wave-Signature",
                "value": "sha256=<hmac_hex>",
                "description": "HMAC-SHA256 of the raw request body, signed with your webhook secret"
              }
            ],
            "url": {
              "raw": "{{base_url}}/v1/webhooks/{{tenant_slug}}/my-source",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "v1",
                "webhooks",
                "{{tenant_slug}}",
                "my-source"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\":  \"{{customer_id}}\",\n  \"event_name\":   \"order_shipped\",\n  \"occurred_at\":  \"2026-07-01T03:00:00Z\",\n  \"properties\": {\n    \"order_id\": \"ORD-8821\",\n    \"carrier\":  \"JNE\",\n    \"tracking\": \"JNE1234567890\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "The URL path encodes both tenant and source:\n- `{{tenant_slug}}` — your tenant slug\n- `my-source` — your webhook source name (any slug you registered in Wave → Sources)\n\n**HMAC signature:**\n```php\n$sig = hash_hmac('sha256', $rawBody, $webhookSecret);\n// Header: X-Wave-Signature: sha256={$sig}\n```\n\nThe body format is the same as the Track Event endpoint."
          }
        }
      ]
    }
  ]
}