{
  "info": {
    "name": "Waaru Developer API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Use a local secret variable for apiKey. Requests are examples; collection execution can mutate resources and send messages. Do not run indiscriminately. Scopes and release contract: developer-openapi.json."
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.waaru.app"
    },
    {
      "key": "apiKey",
      "value": ""
    },
    {
      "key": "recipient",
      "value": ""
    },
    {
      "key": "id",
      "value": ""
    },
    {
      "key": "messageId",
      "value": ""
    },
    {
      "key": "conversationId",
      "value": ""
    },
    {
      "key": "contactId",
      "value": ""
    },
    {
      "key": "labelId",
      "value": ""
    },
    {
      "key": "idempotencyKey",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "listMessageChanges",
      "request": {
        "method": "GET",
        "description": "Requires messages:read. Bootstrap without after and save resumeCursor before retained history reconciliation. Then follow nextCursor, including empty pages, and persist resumeCursor for the next polling cycle. Ordered by immutable workspace revision with a fixed cycle watermark; delivery status comes from that observation, not current message state. Outbound creation status is null. Cursor lease is seven days (410 cursor_expired); invalid or wrong-number cursor is 400 invalid_cursor. history=retained_only: deletions have no tombstones and the lease is not a history-retention promise. No send or reply authority.",
        "header": [],
        "url": "{{baseUrl}}/v1/messages/changes"
      }
    },
    {
      "name": "listCampaigns",
      "request": {
        "method": "GET",
        "description": "Requires campaigns:read. Read this number's campaign summaries. Requires an explicitly granted campaigns:read scope; existing keys gain none. Keep filters unchanged across opaque cursor pages. No schedule, send or revenue-attribution authority.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/campaigns"
      }
    },
    {
      "name": "getCampaign",
      "request": {
        "method": "GET",
        "description": "Requires campaigns:read. Read one authorized campaign and retained settlement/failure cohort counts. Counters describe operational observations, not orders, conversions or revenue.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/campaigns/{{id}}"
      }
    },
    {
      "name": "listCampaignRecipients",
      "request": {
        "method": "GET",
        "description": "Requires campaigns:read. Read paginated retained recipient outcomes for this number's campaign without phone numbers or provider identifiers. Keep filters unchanged; later status changes can change a filtered cohort, so reconcile current results. Retarget evidence does not authorize a new send.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/campaigns/{{id}}/recipients"
      }
    },
    {
      "name": "getInstance",
      "request": {
        "method": "GET",
        "description": "Requires instance:read. Available even when the bound number is disconnected or not in EXTERNAL_API mode. apiAvailable reports connection and handler mode only, never permission to send. No credentials, provider identifiers or diagnostics are returned.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/instance"
      }
    },
    {
      "name": "createLabel",
      "request": {
        "method": "POST",
        "description": "Requires workspace:labels:manage. Body limit: 4096 bytes. Requires an explicitly granted management scope. Archive preserves historical assignments and membership, reserves the name, and rejects active or pinned dynamic-segment dependencies. Repeat archive is safe; creation has no idempotency key.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/labels",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Customers\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "updateLabel",
      "request": {
        "method": "PATCH",
        "description": "Requires workspace:labels:manage. Body limit: 4096 bytes. Requires an explicitly granted management scope. Archive preserves historical assignments and membership, reserves the name, and rejects active or pinned dynamic-segment dependencies. Repeat archive is safe; creation has no idempotency key.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/labels/{{id}}",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Returning customers\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "archiveLabel",
      "request": {
        "method": "POST",
        "description": "Requires workspace:labels:manage. Body limit: 4096 bytes. Requires an explicitly granted management scope. Archive preserves historical assignments and membership, reserves the name, and rejects active or pinned dynamic-segment dependencies. Repeat archive is safe; creation has no idempotency key.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/labels/{{id}}/archive",
        "body": {
          "mode": "raw",
          "raw": "{}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "updateSegment",
      "request": {
        "method": "PATCH",
        "description": "Requires workspace:segments:manage. Body limit: 4096 bytes. Requires an explicitly granted management scope. Archive preserves historical assignments and membership, reserves the name, and rejects active or pinned dynamic-segment dependencies. Repeat archive is safe; creation has no idempotency key.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"Returning customers\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "archiveSegment",
      "request": {
        "method": "POST",
        "description": "Requires workspace:segments:manage. Body limit: 4096 bytes. Requires an explicitly granted management scope. Archive preserves historical assignments and membership, reserves the name, and rejects active or pinned dynamic-segment dependencies. Repeat archive is safe; creation has no idempotency key.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}/archive",
        "body": {
          "mode": "raw",
          "raw": "{}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "sendMessage",
      "request": {
        "method": "POST",
        "description": "Requires messages:send. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          },
          {
            "key": "Idempotency-Key",
            "value": "{{idempotencyKey}}",
            "description": "Set once per logical send; reuse only for exact retries."
          }
        ],
        "url": "{{baseUrl}}/v1/messages",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"messaging_product\": \"whatsapp\",\n  \"to\": \"{{recipient}}\",\n  \"type\": \"text\",\n  \"text\": {\n    \"body\": \"Thanks for your message.\"\n  }\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "getMessage",
      "request": {
        "method": "GET",
        "description": "Requires messages:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/messages/{{messageId}}"
      }
    },
    {
      "name": "listConversations",
      "request": {
        "method": "GET",
        "description": "Requires messages:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/conversations"
      }
    },
    {
      "name": "listMessages",
      "request": {
        "method": "GET",
        "description": "Requires messages:read. Default newest first using before. since or after reads oldest first, using after for subsequent pages. before and after are mutually exclusive. since filters occurredAt; status changes to older messages require callbacks or direct reads.",
        "header": [],
        "url": "{{baseUrl}}/v1/conversations/{{conversationId}}/messages"
      }
    },
    {
      "name": "downloadMedia",
      "request": {
        "method": "GET",
        "description": "Requires media:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/media/{{id}}"
      }
    },
    {
      "name": "listContacts",
      "request": {
        "method": "GET",
        "description": "Requires workspace:contacts:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/contacts"
      }
    },
    {
      "name": "getContact",
      "request": {
        "method": "GET",
        "description": "Requires workspace:contacts:read. Active contacts by default. Explicit includeArchived=true on this exact-ID read exposes archivedAt and lifecycle revision for reviewed restoration; lists remain active-only.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/contacts/{{id}}"
      }
    },
    {
      "name": "upsertContact",
      "request": {
        "method": "POST",
        "description": "Requires workspace:contacts:write. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/contacts",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"phoneE164\": \"{{recipient}}\",\n  \"firstName\": \"Ada\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "updateContact",
      "request": {
        "method": "PATCH",
        "description": "Requires workspace:contacts:write. Update profile fields or explicitly restoreArchived:true with expectedRevision from an exact archived read. Omitted/false never restores; stale archived revision returns 409 contact_archive_changed. Restore and profile changes commit together without changing opt-outs, restrictions or history, or recreating removed segment memberships. Active restoration is a no-op before revision comparison; ordinary profile concurrency is unchanged.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/contacts/{{id}}",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"firstName\": \"Ada\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "listLabels",
      "request": {
        "method": "GET",
        "description": "Requires workspace:contacts:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/labels"
      }
    },
    {
      "name": "applyLabel",
      "request": {
        "method": "PUT",
        "description": "Requires workspace:contacts:write. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/contacts/{{id}}/labels/{{labelId}}"
      }
    },
    {
      "name": "removeLabel",
      "request": {
        "method": "DELETE",
        "description": "Requires workspace:contacts:write. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/contacts/{{id}}/labels/{{labelId}}"
      }
    },
    {
      "name": "listSegments",
      "request": {
        "method": "GET",
        "description": "Requires workspace:segments:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/segments"
      }
    },
    {
      "name": "getSegment",
      "request": {
        "method": "GET",
        "description": "Requires workspace:segments:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}"
      }
    },
    {
      "name": "createSegment",
      "request": {
        "method": "POST",
        "description": "Requires workspace:segments:write. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/developer/segments",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"name\": \"API customers\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "listMembers",
      "request": {
        "method": "GET",
        "description": "Requires workspace:segments:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}/members"
      }
    },
    {
      "name": "addMember",
      "request": {
        "method": "PUT",
        "description": "Requires workspace:segments:write. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}/members/{{contactId}}"
      }
    },
    {
      "name": "removeMember",
      "request": {
        "method": "DELETE",
        "description": "Requires workspace:segments:write. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/segments/{{id}}/members/{{contactId}}"
      }
    },
    {
      "name": "listTemplates",
      "request": {
        "method": "GET",
        "description": "Requires templates:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/templates"
      }
    },
    {
      "name": "getTemplate",
      "request": {
        "method": "GET",
        "description": "Requires templates:read. Unavailable cached templates return lifecycle metadata without live Form discovery. Approved available templates may include the sanitized live launch contract. Neither inspection nor a null unavailableReason authorizes sending.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/templates/{{id}}"
      }
    },
    {
      "name": "getActivity",
      "request": {
        "method": "GET",
        "description": "Requires reports:read. Fixed seven UTC days. API calls belong to this key; callback summaries belong to its number. No message content or recipients.",
        "header": [],
        "url": "{{baseUrl}}/v1/developer/reports/activity"
      }
    },
    {
      "name": "getWebhook",
      "request": {
        "method": "GET",
        "description": "Requires webhooks:read. ",
        "header": [],
        "url": "{{baseUrl}}/v1/webhook"
      }
    },
    {
      "name": "setWebhook",
      "request": {
        "method": "PUT",
        "description": "Requires webhooks:manage. Use null revision to create. Updates require current revision and resume the endpoint. A new signing secret is returned only upon creation.",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/webhook",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"revision\": null,\n  \"callbackUrl\": \"https://example.com/waaru/events\",\n  \"selectedEvents\": [\n    \"MESSAGE_RECEIVED\"\n  ]\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "rotateWebhook",
      "request": {
        "method": "POST",
        "description": "Requires webhooks:manage. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/webhook/rotate",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"revision\": 1\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "testWebhook",
      "request": {
        "method": "POST",
        "description": "Requires webhooks:manage. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/webhook/test",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"revision\": 1\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "setWebhookState",
      "request": {
        "method": "POST",
        "description": "Requires webhooks:manage. ",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "url": "{{baseUrl}}/v1/webhook/state",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"revision\": 1,\n  \"state\": \"DISABLED\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    }
  ]
}
