{
  "info": {
    "name": "Petasos Partner API",
    "description": "Programmatic access to Petasos for WhatsApp blast operations. This collection targets production (`api.petasos.id`) by default. Also import one of the two companion Postman Environments -- `petasos-staging.postman_environment.json` or `petasos-production.postman_environment.json` (both downloadable from the same API Docs page as this collection) -- and select it from Postman's environment dropdown (top right) to switch `base_url`/`iris_base_url` between staging and production without editing anything here. Set the `api_key` collection variable before sending requests (Settings → API Keys → New key in your Partner Dashboard). The Room requests below are a separate product (Iris live chat) with their own key and domain -- set `iris_api_key` too (generated in Iris's own Settings page, not the Partner Dashboard) -- the raw key is shown once, at creation, either way.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{api_key}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.petasos.id/v1",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "wk_YOUR_API_KEY",
      "type": "string"
    },
    {
      "key": "trace_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "campaign_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "device_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "device_group_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "label_name",
      "value": "",
      "type": "string"
    },
    {
      "key": "broadcast_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "device_name",
      "value": "",
      "type": "string"
    },
    {
      "key": "room_id",
      "value": "",
      "type": "string"
    },
    {
      "key": "iris_base_url",
      "value": "https://iris-api.petasos.id/v1",
      "type": "string"
    },
    {
      "key": "iris_api_key",
      "value": "wk_YOUR_IRIS_API_KEY",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "List Devices",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/devices",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "devices"
          ]
        },
        "description": "Requires scope: manage_devices. Every device provisioned for your account -- look up a device_id to use in Create Campaign, without ever needing to track an internal ID anywhere else."
      }
    },
    {
      "name": "List Device Groups",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/device-groups",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "device-groups"
          ]
        },
        "description": "Requires scope: manage_devices. Every device group on your account -- look up a device_group_id to use in Create Campaign instead of a single device_id. Each group's devices list only includes currently-connected members."
      }
    },
    {
      "name": "Create Campaign",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"July Promo\",\n  \"template\": \"Hi {name}, enjoy 20% off this week!\",\n  \"device_id\": \"{{device_id}}\"\n}"
        },
        "url": {
          "raw": "{{base_url}}/campaigns",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns"
          ]
        },
        "description": "Requires scope: send_message. source is always \"api\" -- there's no field for it, and no way to override it. Comes back status: \"draft\"; activate it from your dashboard's Campaigns page before it accepts sends. Exactly one of device_id/device_group_id is required."
      }
    },
    {
      "name": "List Campaigns",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/campaigns?status=active",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns"
          ],
          "query": [
            {
              "key": "status",
              "value": "active"
            }
          ]
        },
        "description": "Requires scope: send_message. Always scoped to source: \"api\"; defaults to status=active, but any status can be requested explicitly."
      }
    },
    {
      "name": "Send Campaign Message",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"+628123456789\",\n  \"variables\": { \"name\": \"Budi\" }\n}"
        },
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/messages",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "messages"
          ]
        },
        "description": "Requires scope: send_message. Campaign must belong to your account, have source: \"api\", and be status: \"active\"."
      }
    },
    {
      "name": "Send Campaign Message (with image/file)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phone\": \"+628123456789\",\n  \"variables\": { \"name\": \"Budi\" },\n  \"media_url\": \"https://cdn.example.com/promo.jpg\",\n  \"media_mimetype\": \"image/jpeg\",\n  \"media_filename\": \"promo.jpg\"\n}"
        },
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/messages",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "messages"
          ]
        },
        "description": "Same endpoint as above, attaching an image or PDF. Use media_url (a file you already host) or media_base64 (raw file data) -- never both -- with media_mimetype required either way. Images and application/pdf only, up to 16MB."
      }
    },
    {
      "name": "Send Campaign Message (upload file directly, form-data)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "formdata",
          "formdata": [
            {
              "key": "phone",
              "value": "+628123456789",
              "type": "text"
            },
            {
              "key": "variables",
              "value": "{\"name\": \"Budi\"}",
              "type": "text"
            },
            {
              "key": "file",
              "type": "file",
              "src": []
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/messages",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "messages"
          ]
        },
        "description": "Same endpoint as above -- attach a file directly instead of hosting it or base64-encoding it. Click the file field and select an image/PDF from your computer; Content-Type is set automatically by Postman, don't set it by hand. media_mimetype is inferred from the file if you don't add it as a separate text field."
      }
    },
    {
      "name": "Send Broadcast (to a label)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"label_name\": \"{{label_name}}\",\n  \"variables\": { \"promo_code\": \"SAVE20\" }\n}"
        },
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/broadcasts",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "broadcasts"
          ]
        },
        "description": "Requires scope: send_message. Sends to every contact carrying label_name (labels are unique per account by name -- the dashboard never shows a label's ID). Responds 202 immediately with a broadcast_id -- the actual fan-out happens in the background regardless of label size. variables are uniform defaults; each contact's own stored fields override them on conflict. Same media_url/media_base64/media_mimetype/media_filename fields as Send Campaign Message are accepted, as one shared attachment for the whole broadcast."
      }
    },
    {
      "name": "Send Broadcast (upload file directly, form-data)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "body": {
          "mode": "formdata",
          "formdata": [
            {
              "key": "label_name",
              "value": "{{label_name}}",
              "type": "text"
            },
            {
              "key": "variables",
              "value": "{\"promo_code\": \"SAVE20\"}",
              "type": "text"
            },
            {
              "key": "file",
              "type": "file",
              "src": []
            }
          ]
        },
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/broadcasts",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "broadcasts"
          ]
        },
        "description": "Same endpoint as Send Broadcast -- attach the shared attachment directly instead of hosting it or base64-encoding it."
      }
    },
    {
      "name": "Get Broadcast Status",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/broadcasts/{{broadcast_id}}",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "broadcasts",
            "{{broadcast_id}}"
          ]
        },
        "description": "Requires scope: read_status. Use the broadcast_id returned by Send Broadcast. status is pending/processing/completed/error; contact_count is set immediately, the sent/scheduled/skipped_opted_out/failed counts fill in once processing completes."
      }
    },
    {
      "name": "List Broadcast Messages",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/campaigns/{{campaign_id}}/broadcasts/{{broadcast_id}}/messages?limit=50&offset=0",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "campaigns",
            "{{campaign_id}}",
            "broadcasts",
            "{{broadcast_id}}",
            "messages"
          ],
          "query": [
            {
              "key": "limit",
              "value": "50"
            },
            {
              "key": "offset",
              "value": "0"
            }
          ]
        },
        "description": "Requires scope: read_status. Lists the trace_id/phone/status of every individual recipient message this broadcast fanned out -- use this alongside Get Broadcast Status if you need per-recipient detail, not just aggregate counts."
      }
    },
    {
      "name": "Get Message Status",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/messages/{{trace_id}}",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "messages",
            "{{trace_id}}"
          ]
        },
        "description": "Requires scope: read_status. Use the trace_id returned when the message was sent via Send Campaign Message."
      }
    },
    {
      "name": "Connect Device (get QR code)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/devices/{{device_name}}/qr",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "devices",
            "{{device_name}}",
            "qr"
          ]
        },
        "description": "Requires scope: manage_devices. Starts/refreshes the device's WhatsApp session and returns a QR code (qr_image_base64) to scan, or connected: true if already paired. The device must already exist -- created by an internal admin, same as your dashboard; this only drives its connection state. Poll every 2-3s while showing the QR."
      }
    },
    {
      "name": "Disconnect Device",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "url": {
          "raw": "{{base_url}}/devices/{{device_name}}/disconnect",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "devices",
            "{{device_name}}",
            "disconnect"
          ]
        },
        "description": "Requires scope: manage_devices. Unlinks the currently-paired WhatsApp number, keeping the device slot (name, group, history) ready for a fresh QR scan via Connect Device."
      }
    },
    {
      "name": "Get Device Status",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{base_url}}/devices/{{device_name}}/status",
          "host": [
            "{{base_url}}"
          ],
          "path": [
            "devices",
            "{{device_name}}",
            "status"
          ]
        },
        "description": "Requires scope: manage_devices. Live check against the device's current WhatsApp connection, rather than the last value your dashboard showed."
      }
    },
    {
      "name": "List Rooms",
      "request": {
        "method": "GET",
        "header": [],
        "auth": {
          "type": "apikey",
          "apikey": [
            {
              "key": "key",
              "value": "X-API-Key",
              "type": "string"
            },
            {
              "key": "value",
              "value": "{{iris_api_key}}",
              "type": "string"
            },
            {
              "key": "in",
              "value": "header",
              "type": "string"
            }
          ]
        },
        "url": {
          "raw": "{{iris_base_url}}/rooms?status=open",
          "host": [
            "{{iris_base_url}}"
          ],
          "path": [
            "rooms"
          ],
          "query": [
            {
              "key": "status",
              "value": "open"
            }
          ]
        },
        "description": "Requires scope: manage_rooms, on Iris's own separate API key (not the base_url/api_key above). Lists your Iris rooms, most recently active first. status is optional (open/closed, omit for both)."
      }
    },
    {
      "name": "Get Room",
      "request": {
        "method": "GET",
        "header": [],
        "auth": {
          "type": "apikey",
          "apikey": [
            {
              "key": "key",
              "value": "X-API-Key",
              "type": "string"
            },
            {
              "key": "value",
              "value": "{{iris_api_key}}",
              "type": "string"
            },
            {
              "key": "in",
              "value": "header",
              "type": "string"
            }
          ]
        },
        "url": {
          "raw": "{{iris_base_url}}/rooms/{{room_id}}?limit=100&offset=0",
          "host": [
            "{{iris_base_url}}"
          ],
          "path": [
            "rooms",
            "{{room_id}}"
          ],
          "query": [
            {
              "key": "limit",
              "value": "100"
            },
            {
              "key": "offset",
              "value": "0"
            }
          ]
        },
        "description": "Requires scope: manage_rooms, on Iris's own separate API key (not the base_url/api_key above). Returns the room plus its message history."
      }
    },
    {
      "name": "Send Room Message",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "auth": {
          "type": "apikey",
          "apikey": [
            {
              "key": "key",
              "value": "X-API-Key",
              "type": "string"
            },
            {
              "key": "value",
              "value": "{{iris_api_key}}",
              "type": "string"
            },
            {
              "key": "in",
              "value": "header",
              "type": "string"
            }
          ]
        },
        "body": {
          "mode": "raw",
          "raw": "{\n  \"body\": \"Yes, still in stock!\"\n}"
        },
        "url": {
          "raw": "{{iris_base_url}}/rooms/{{room_id}}/messages",
          "host": [
            "{{iris_base_url}}"
          ],
          "path": [
            "rooms",
            "{{room_id}}",
            "messages"
          ]
        },
        "description": "Requires scope: manage_rooms, on Iris's own separate API key (not the base_url/api_key above). Sends a reply into an open room, attributed to no specific human agent (sender_user_id is null) -- never blocked by an agent clock-in check. At least one of body/media_url is required; media_url must already be a hosted, reachable file."
      }
    },
    {
      "name": "Close Room",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}"
          }
        ],
        "auth": {
          "type": "apikey",
          "apikey": [
            {
              "key": "key",
              "value": "X-API-Key",
              "type": "string"
            },
            {
              "key": "value",
              "value": "{{iris_api_key}}",
              "type": "string"
            },
            {
              "key": "in",
              "value": "header",
              "type": "string"
            }
          ]
        },
        "url": {
          "raw": "{{iris_base_url}}/rooms/{{room_id}}/close",
          "host": [
            "{{iris_base_url}}"
          ],
          "path": [
            "rooms",
            "{{room_id}}",
            "close"
          ]
        },
        "description": "Requires scope: manage_rooms, on Iris's own separate API key (not the base_url/api_key above). Idempotent -- closing an already-closed room is a no-op success."
      }
    }
  ]
}
