{
  "info": {
    "name": "RelayWA customer API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Set baseUrl/apiKey/phone/requestId. Keep requestId stable for a retry, change it for a new send. Each accepted message uses INR wallet credit at the stored per-type price. Fund wallets through the dashboard using UPI sumanmalikrepo@slc and submit transaction details for admin verification; a submission is not a credit. Worker credentials are never used."
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://YOUR_GATEWAY_ORIGIN",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "phone",
      "value": "+12025550123",
      "type": "string"
    },
    {
      "key": "requestId",
      "value": "replace-with-unique-logical-request",
      "type": "string"
    },
    {
      "key": "mediaId",
      "value": "11111111-1111-4111-8111-111111111111",
      "type": "string"
    },
    {
      "key": "uploadId",
      "value": "",
      "type": "string"
    },
    {
      "key": "partNumber",
      "value": "1",
      "type": "string"
    },
    {
      "key": "jobId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Message pricing",
      "item": [
        {
          "name": "Current public INR rates",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/api/public/plans",
            "auth": {
              "type": "noauth"
            },
            "description": "Read enabled message prices. This public catalogue does not expose wallet balances; manage funding and history in the dashboard."
          }
        }
      ]
    },
    {
      "name": "Messages",
      "item": [
        {
          "name": "Send text",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "text-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"text\",\n  \"message\": \"Hello from RelayWA\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send image",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "image-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"image\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"caption\": \"A photo\",\n  \"viewOnce\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send video",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "video-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"video\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"caption\": \"A video\",\n  \"gifPlayback\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send audio",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "audio-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"audio\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"ptt\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send voice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "voice-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"voice\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"seconds\": 12\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send document",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "document-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"document\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"fileName\": \"invoice.pdf\",\n  \"caption\": \"Your invoice\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send sticker",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "sticker-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"sticker\",\n  \"mediaId\": \"{{mediaId}}\",\n  \"isAnimated\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send location",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "location-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"location\",\n  \"location\": {\n    \"latitude\": 22.5726,\n    \"longitude\": 88.3639,\n    \"name\": \"Meeting point\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send contacts",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "contacts-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"contacts\",\n  \"contacts\": [\n    {\n      \"name\": \"Example Contact\",\n      \"phone\": \"+12025550124\",\n      \"organization\": \"Example\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send poll",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "poll-{{requestId}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"{{phone}}\",\n  \"type\": \"poll\",\n  \"poll\": {\n    \"name\": \"Choose a time\",\n    \"options\": [\n      \"Morning\",\n      \"Afternoon\"\n    ],\n    \"selectableCount\": 1\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/v1/messages",
            "description": "New logical send: change requestId. Network retry: preserve requestId and payload. Media must already be ready. One INR wallet charge on durable acceptance; insufficient credit returns 402 with no job. Confirmed pre-send failure/cancellation refunds once. Never automatically resend unknown outcomes."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('jobId', pm.response.json().jobId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Read job",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/v1/messages/{{jobId}}"
          }
        },
        {
          "name": "Message history",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/v1/messages?limit=25"
          }
        }
      ]
    },
    {
      "name": "Media",
      "item": [
        {
          "name": "Create upload ticket",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/v1/media/uploads",
            "description": "Requires media:write. Set size to the actual total file bytes before sending. The response stores mediaId/uploadId. In multipart mode use the returned partSize, upload all raw binary parts, then complete and poll media.status until ready. Import/upload acceptance alone does not permit sending the file.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"document\",\n  \"size\": 123,\n  \"fileName\": \"invoice.pdf\",\n  \"mimetype\": \"application/pdf\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) { const ticket = pm.response.json(); pm.collectionVariables.set('mediaId', ticket.mediaId); pm.collectionVariables.set('uploadId', ticket.uploadId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Upload one binary multipart part",
          "request": {
            "method": "PUT",
            "url": "{{baseUrl}}/v1/media/uploads/{{mediaId}}/parts/{{partNumber}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/octet-stream"
              },
              {
                "key": "X-Upload-Id",
                "value": "{{uploadId}}"
              }
            ],
            "body": {
              "mode": "file",
              "file": {
                "src": ""
              }
            },
            "description": "Multipart mode only; requires media:write. Choose a local binary part in Body → binary. Each non-final part must have exactly partSize bytes; the final part contains the remaining bytes. Postman calculates Content-Length from the selected file. Do not send multipart/form-data. Repeat with partNumber=2,3,... for all parts before completion. The 15-minute ticket expires even if an upload is incomplete."
          }
        },
        {
          "name": "Complete upload and queue inspection",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/v1/media/uploads/{{mediaId}}/complete",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Requires media:write. Call after all binary parts or the optional direct upload have succeeded. Response 202 is not ready: poll Read media state and wait for response.media.status === ready before sending."
          }
        },
        {
          "name": "Import HTTPS image",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/v1/media/imports",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"image\",\n  \"url\": \"https://example.com/photo.png\"\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 202) { pm.collectionVariables.set('mediaId', pm.response.json().mediaId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Read media state",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/v1/media/{{mediaId}}"
          }
        }
      ]
    },
    {
      "name": "Session management (extra scope)",
      "item": [
        {
          "name": "Connection state",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/v1/session"
          }
        },
        {
          "name": "Request fresh QR",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/v1/session/pairing",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\"fresh\":true}"
            }
          }
        },
        {
          "name": "Read QR",
          "request": {
            "method": "GET",
            "url": "{{baseUrl}}/v1/session/qr"
          }
        },
        {
          "name": "Disconnect and release",
          "request": {
            "method": "POST",
            "url": "{{baseUrl}}/v1/session/disconnect"
          }
        }
      ]
    }
  ]
}
