{
	"info": {
		"_postman_id": "selfhostedsms-api",
		"name": "SelfHostedSMS API",
		"description": "API for managing SMS queue and Android app versions.\n\nDocumentation: /postman",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"variable": [
		{
			"key": "base_url",
			"value": "{{base_url}}",
			"type": "string"
		},
		{
			"key": "api_token",
			"value": "{{api_token}}",
			"type": "string"
		}
	],
	"item": [
		{
			"name": "App Version",
			"description": "Public endpoints for Android app version management. No authentication required.",
			"item": [
				{
					"name": "Get Current Version",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"url": {
							"raw": "{{base_url}}/app-version",
							"host": ["{{base_url}}"],
							"path": ["app-version"]
						},
						"description": "## Get Current Version\n\nRetrieve information about the currently active app version.\n\n### Authentication\nNo authentication required - this is a public endpoint.\n\n### Response (200 OK)\n```json\n{\n  \"version\": \"1.2.0\",\n  \"versionCode\": 5,\n  \"downloadUrl\": \"https://selfhostedsms.com/download/app.apk\",\n  \"releaseNotes\": \"- Bug fixes\\n- Performance improvements\",\n  \"fileSize\": 15728640,\n  \"publishedAt\": \"2026-01-26T12:00:00+00:00\"\n}\n```\n\n### Error Responses\n\n**404 Not Found** - No active version:\n```json\n{\n  \"error\": \"No active version available\",\n  \"code\": \"no_active_version\"\n}\n```"
					},
					"response": [
						{
							"name": "Success",
							"status": "OK",
							"code": 200,
							"body": "{\n\t\"version\": \"1.2.0\",\n\t\"versionCode\": 5,\n\t\"downloadUrl\": \"https://selfhostedsms.com/download/app.apk\",\n\t\"releaseNotes\": \"- Bug fixes\\n- Performance improvements\",\n\t\"fileSize\": 15728640,\n\t\"publishedAt\": \"2026-01-26T12:00:00+00:00\"\n}"
						},
						{
							"name": "No Active Version",
							"status": "Not Found",
							"code": 404,
							"body": "{\n\t\"error\": \"No active version available\",\n\t\"code\": \"no_active_version\"\n}"
						}
					]
				},
				{
					"name": "List All Versions",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"url": {
							"raw": "{{base_url}}/app-versions",
							"host": ["{{base_url}}"],
							"path": ["app-versions"]
						},
						"description": "## List All Versions\n\nRetrieve a list of all published app versions, sorted by version code (newest first).\n\n### Authentication\nNo authentication required - this is a public endpoint.\n\n### Response (200 OK)\n```json\n{\n  \"versions\": [\n    {\n      \"version\": \"1.2.0\",\n      \"versionCode\": 5,\n      \"isActive\": true,\n      \"downloadUrl\": \"https://selfhostedsms.com/api/download/5\",\n      \"releaseNotes\": \"- Bug fixes\\n- Performance improvements\",\n      \"fileSize\": 15728640,\n      \"publishedAt\": \"2026-01-26T12:00:00+00:00\"\n    },\n    {\n      \"version\": \"1.1.0\",\n      \"versionCode\": 4,\n      \"isActive\": false,\n      \"downloadUrl\": \"https://selfhostedsms.com/api/download/4\",\n      \"releaseNotes\": \"- Initial release\",\n      \"fileSize\": 14680064,\n      \"publishedAt\": \"2026-01-20T10:00:00+00:00\"\n    }\n  ]\n}\n```"
					},
					"response": [
						{
							"name": "Success",
							"status": "OK",
							"code": 200,
							"body": "{\n\t\"versions\": [\n\t\t{\n\t\t\t\"version\": \"1.2.0\",\n\t\t\t\"versionCode\": 5,\n\t\t\t\"isActive\": true,\n\t\t\t\"downloadUrl\": \"https://selfhostedsms.com/api/download/5\",\n\t\t\t\"releaseNotes\": \"- Bug fixes\\n- Performance improvements\",\n\t\t\t\"fileSize\": 15728640,\n\t\t\t\"publishedAt\": \"2026-01-26T12:00:00+00:00\"\n\t\t}\n\t]\n}"
						}
					]
				},
				{
					"name": "Download APK (Active)",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{base_url}}/../download/app.apk",
							"host": ["{{base_url}}"],
							"path": ["..", "download", "app.apk"]
						},
						"description": "## Download APK (Active Version)\n\nDownload the currently active APK file.\n\n### Authentication\nNo authentication required - this is a public endpoint.\n\n### Response (200 OK)\nBinary APK file download with headers:\n- `Content-Type: application/vnd.android.package-archive`\n- `Content-Disposition: attachment; filename=\"app-v1.2.0.apk\"`\n\n### Error Responses\n\n**404 Not Found** - No active version or file missing:\n```json\n{\n  \"error\": \"No active version available\",\n  \"code\": \"no_active_version\"\n}\n```"
					},
					"response": []
				},
				{
					"name": "Download APK (By Version Code)",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{base_url}}/download/5",
							"host": ["{{base_url}}"],
							"path": ["download", "5"]
						},
						"description": "## Download APK by Version Code\n\nDownload a specific APK file by its version code.\n\n### Authentication\nNo authentication required - this is a public endpoint.\n\n### URL Parameters\n\n| Parameter | Required | Description |\n|-----------|----------|-------------|\n| version_code | Yes | Numeric version code |\n\n### Response (200 OK)\nBinary APK file download with headers:\n- `Content-Type: application/vnd.android.package-archive`\n- `Content-Disposition: attachment; filename=\"app-v1.2.0.apk\"`\n\n### Error Responses\n\n**404 Not Found** - Version not found:\n```json\n{\n  \"error\": \"Version not found\",\n  \"code\": \"version_not_found\"\n}\n```"
					},
					"response": []
				}
			]
		},
		{
			"name": "SMS Queue",
			"description": "Endpoints for managing SMS queue. Requires API token authentication.",
			"item": [
				{
					"name": "Queue Messages",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							},
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "[\n\t{\n\t\t\"phone\": \"+1234567890\",\n\t\t\"message\": \"Hello, this is a test message\"\n\t},\n\t{\n\t\t\"phone\": \"0712345678\",\n\t\t\"message\": \"Another test message\"\n\t}\n]"
						},
						"url": {
							"raw": "{{base_url}}/{{api_token}}/queue",
							"host": ["{{base_url}}"],
							"path": ["{{api_token}}", "queue"]
						},
						"description": "## Queue Messages\n\nAdd one or more SMS messages to the queue for the authenticated user.\n\n### Authentication\nAPI token passed as URL parameter.\n\n### Request Body\n\nAccepts an array of messages directly or wrapped in a `messages` key:\n\n**Direct array:**\n```json\n[\n  {\"phone\": \"+1234567890\", \"message\": \"Hello world\"},\n  {\"phone\": \"0712345678\", \"text\": \"Another message\"}\n]\n```\n\n**Wrapped format:**\n```json\n{\n  \"messages\": [\n    {\"phone\": \"+1234567890\", \"message\": \"Hello world\"}\n  ]\n}\n```\n\n### Field Validation\n\n| Field | Required | Rules |\n|-------|----------|-------|\n| phone | Yes | 6-20 digits, optionally starting with `+` |\n| message/text | Yes | Max 1000 characters |\n\n### Response (200 OK)\n```json\n{\n  \"ok\": true,\n  \"queued\": 2,\n  \"failed\": 0,\n  \"items\": [\n    {\"index\": 0, \"hash\": \"a1b2c3d4e5f6g7h8\", \"phone\": \"+1234567890\"},\n    {\"index\": 1, \"hash\": \"i9j0k1l2m3n4o5p6\", \"phone\": \"0712345678\"}\n  ],\n  \"errors\": []\n}\n```\n\n### Error Responses\n\n**422 Unprocessable Entity** - Validation errors:\n```json\n{\n  \"ok\": false,\n  \"queued\": 0,\n  \"failed\": 2,\n  \"items\": [],\n  \"errors\": [\n    {\"index\": 0, \"error\": \"Invalid phone format. Use digits only, optionally starting with +, 6-20 digits\"},\n    {\"index\": 1, \"error\": \"Message is required\"}\n  ]\n}\n```\n\n**403 Forbidden** - No active subscription:\n```json\n{\n  \"error\": \"No active subscription\",\n  \"code\": \"subscription_required\"\n}\n```"
					},
					"response": [
						{
							"name": "Success",
							"status": "OK",
							"code": 200,
							"body": "{\n\t\"ok\": true,\n\t\"queued\": 2,\n\t\"failed\": 0,\n\t\"items\": [\n\t\t{\"index\": 0, \"hash\": \"a1b2c3d4e5f6g7h8\", \"phone\": \"+1234567890\"},\n\t\t{\"index\": 1, \"hash\": \"i9j0k1l2m3n4o5p6\", \"phone\": \"0712345678\"}\n\t],\n\t\"errors\": []\n}"
						},
						{
							"name": "Validation Error",
							"status": "Unprocessable Entity",
							"code": 422,
							"body": "{\n\t\"ok\": false,\n\t\"queued\": 0,\n\t\"failed\": 1,\n\t\"items\": [],\n\t\"errors\": [\n\t\t{\"index\": 0, \"error\": \"Invalid phone format. Use digits only, optionally starting with +, 6-20 digits\"}\n\t]\n}"
						}
					]
				},
				{
					"name": "Get Messages",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"url": {
							"raw": "{{base_url}}/{{api_token}}/get_messages?limit=20",
							"host": ["{{base_url}}"],
							"path": ["{{api_token}}", "get_messages"],
							"query": [
								{
									"key": "limit",
									"value": "20",
									"description": "Number of messages to retrieve (default: 20, max: 200)"
								}
							]
						},
						"description": "## Get Messages\n\nRetrieve pending SMS messages from the queue. Messages are returned in FIFO order (oldest first) and automatically marked as `initiated` (status 1).\n\n### Authentication\nAPI token passed as URL parameter.\n\n### Query Parameters\n\n| Parameter | Required | Default | Max | Description |\n|-----------|----------|---------|-----|-------------|\n| limit | No | 20 | 200 | Number of messages to retrieve |\n\n### Response (200 OK)\n```json\n[\n  {\n    \"phone\": \"+1234567890\",\n    \"text\": \"Hello world\",\n    \"hash\": \"a1b2c3d4e5f6g7h8\"\n  },\n  {\n    \"phone\": \"0712345678\",\n    \"text\": \"Another message\",\n    \"hash\": \"i9j0k1l2m3n4o5p6\"\n  }\n]\n```\n\n### Behavior\n- Returns messages with status `0` (new)\n- Automatically updates returned messages to status `1` (initiated)\n- Increments retry counter for each message\n- Respects user's monthly SMS quota (if applicable)\n- Returns empty array `[]` if no pending messages\n\n### Error Responses\n\n**403 Forbidden** - No active subscription:\n```json\n{\n  \"error\": \"No active subscription\",\n  \"code\": \"subscription_required\"\n}\n```\n\n**429 Too Many Requests** - SMS limit exceeded:\n```json\n{\n  \"error\": \"SMS limit exceeded for this month\",\n  \"code\": \"limit_exceeded\",\n  \"limit\": 1000,\n  \"used\": 1000\n}\n```"
					},
					"response": [
						{
							"name": "Success with messages",
							"status": "OK",
							"code": 200,
							"body": "[\n\t{\n\t\t\"phone\": \"+1234567890\",\n\t\t\"text\": \"Hello world\",\n\t\t\"hash\": \"a1b2c3d4e5f6g7h8\"\n\t},\n\t{\n\t\t\"phone\": \"0712345678\",\n\t\t\"text\": \"Another message\",\n\t\t\"hash\": \"i9j0k1l2m3n4o5p6\"\n\t}\n]"
						},
						{
							"name": "Empty queue",
							"status": "OK",
							"code": 200,
							"body": "[]"
						}
					]
				},
				{
					"name": "Update Status (Batch)",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							},
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n\t\"device\": \"MyPhone\",\n\t\"messages\": [\n\t\t{\n\t\t\t\"hash\": \"a1b2c3d4e5f67890\",\n\t\t\t\"status\": 2\n\t\t},\n\t\t{\n\t\t\t\"hash\": \"f8e7d6c5b4a32109\",\n\t\t\t\"status\": 3\n\t\t}\n\t]\n}"
						},
						"url": {
							"raw": "{{base_url}}/{{api_token}}/status",
							"host": ["{{base_url}}"],
							"path": ["{{api_token}}", "status"]
						},
						"description": "## Update Status (Batch)\n\nUpdate the delivery status of one or more SMS messages.\n\n### Authentication\nAPI token passed as URL parameter.\n\n### Request Body\n\n```json\n{\n  \"device\": \"MyPhone\",\n  \"messages\": [\n    {\"hash\": \"a1b2c3d4e5f6g7h8\", \"status\": 2},\n    {\"hash\": \"i9j0k1l2m3n4o5p6\", \"status\": 3}\n  ]\n}\n```\n\n### Fields\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| device | No | Device name that sent the messages (max 15 chars) |\n| messages | Yes | Array of status updates |\n| messages[].hash | Yes | Message hash (max 64 characters) |\n| messages[].status | Yes | Must be 2 (success) or 3 (fail) |\n\n### Status Codes\n\n| Status | Meaning |\n|--------|---------|\n| 2 | Success - Message delivered |\n| 3 | Fail - Message failed to deliver |\n\n### Response (200 OK)\n```json\n{\n  \"ok\": true,\n  \"updated\": 2\n}\n```\n\n### Error Responses\n\n**403 Forbidden** - No active subscription:\n```json\n{\n  \"error\": \"No active subscription\",\n  \"code\": \"subscription_required\"\n}\n```"
					},
					"response": [
						{
							"name": "Success",
							"status": "OK",
							"code": 200,
							"body": "{\n\t\"ok\": true,\n\t\"updated\": 2\n}"
						}
					]
				},
				{
					"name": "Update Status (Single)",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							},
							{
								"key": "Accept",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n\t\"status\": 2\n}"
						},
						"url": {
							"raw": "{{base_url}}/{{api_token}}/status/a1b2c3d4e5f67890",
							"host": ["{{base_url}}"],
							"path": ["{{api_token}}", "status", "a1b2c3d4e5f67890"]
						},
						"description": "## Update Status (Single)\n\nUpdate the delivery status of a single SMS message using the hash in the URL.\n\n### Authentication\nAPI token passed as URL parameter.\n\n### URL Parameters\n\n| Parameter | Required | Description |\n|-----------|----------|-------------|\n| hash | Yes | The message hash (max 64 characters) |\n\n### Request Body\n```json\n{\n  \"status\": 2\n}\n```\n\n### Status Codes\n\n| Status | Meaning |\n|--------|---------|\n| 2 | Success - Message delivered |\n| 3 | Fail - Message failed to deliver |\n\n### Response (200 OK)\n```json\n{\n  \"ok\": true,\n  \"updated\": 1\n}\n```\n\n### Error Responses\n\n**403 Forbidden** - No active subscription:\n```json\n{\n  \"error\": \"No active subscription\",\n  \"code\": \"subscription_required\"\n}\n```"
					},
					"response": [
						{
							"name": "Success",
							"status": "OK",
							"code": 200,
							"body": "{\n\t\"ok\": true,\n\t\"updated\": 1\n}"
						}
					]
				}
			]
		}
	]
}
