diff --git a/api/docs/docs.go b/api/docs/docs.go index ed279605..dadff723 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -144,6 +144,44 @@ const docTemplate = `{ } }, "/bulk-messages": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Fetches the last 10 bulk message order summaries for the authenticated user showing counts per status.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "BulkSMS" + ], + "summary": "List bulk message orders", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.BulkMessagesResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, "post": { "security": [ { @@ -1759,6 +1797,12 @@ const docTemplate = `{ "$ref": "#/definitions/responses.Unauthorized" } }, + "402": { + "description": "Payment Required", + "schema": { + "$ref": "#/definitions/responses.PaymentRequired" + } + }, "422": { "description": "Unprocessable Entity", "schema": { @@ -3299,6 +3343,53 @@ const docTemplate = `{ } } }, + "entities.BulkMessage": { + "type": "object", + "required": [ + "created_at", + "delivered_count", + "failed_count", + "pending_count", + "request_id", + "scheduled_count", + "sent_count", + "total" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "delivered_count": { + "type": "integer", + "example": 25 + }, + "failed_count": { + "type": "integer", + "example": 5 + }, + "pending_count": { + "type": "integer", + "example": 30 + }, + "request_id": { + "type": "string", + "example": "bulk-32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "scheduled_count": { + "type": "integer", + "example": 50 + }, + "sent_count": { + "type": "integer", + "example": 40 + }, + "total": { + "type": "integer", + "example": 150 + } + } + }, "entities.Discord": { "type": "object", "required": [ @@ -4599,6 +4690,30 @@ const docTemplate = `{ } } }, + "responses.BulkMessagesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.BulkMessage" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } + }, "responses.DiscordResponse": { "type": "object", "required": [ diff --git a/api/docs/swagger.json b/api/docs/swagger.json index 656ee381..69e9d363 100644 --- a/api/docs/swagger.json +++ b/api/docs/swagger.json @@ -1,4760 +1,5370 @@ { - "schemes": ["https"], - "swagger": "2.0", - "info": { - "description": "Use your Android phone to send and receive SMS messages via a simple programmable API with end-to-end encryption.", - "title": "httpSMS API Reference", - "contact": { - "name": "support@httpsms.com", - "email": "support@httpsms.com" - }, - "license": { - "name": "AGPL-3.0", - "url": "https://raw.githubusercontent.com/NdoleStudio/http-sms-manager/main/LICENSE" - }, - "version": "1.0" - }, - "host": "api.httpsms.com", - "basePath": "/v1", - "paths": { - "/billing/usage": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get the summary of sent and received messages for a user in the current month", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Billing"], - "summary": "Get Billing Usage.", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.BillingUsageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/billing/usage-history": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get billing usage records of sent and received messages for a user in the past. It will be sorted by timestamp in descending order.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Billing"], - "summary": "Get billing usage history.", - "parameters": [ - { - "minimum": 0, - "type": "integer", - "description": "number of heartbeats to skip", - "name": "skip", - "in": "query" - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "description": "number of heartbeats to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.BillingUsagesResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/bulk-messages": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Sends bulk SMS messages to multiple users based on our [CSV template](https://httpsms.com/templates/httpsms-bulk.csv) or our [Excel template](https://httpsms.com/templates/httpsms-bulk.xlsx).", - "consumes": ["multipart/form-data"], - "produces": ["application/json"], - "tags": ["BulkSMS"], - "summary": "Store bulk SMS file", - "parameters": [ - { - "type": "file", - "description": "The Excel or CSV file containing the messages to be sent.", - "name": "document", - "in": "formData", - "required": true - } - ], - "responses": { - "202": { - "description": "Accepted", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/discord-integrations": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get the discord integrations of a user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["DiscordIntegration"], - "summary": "Get discord integrations of a user", - "parameters": [ - { - "minimum": 0, - "type": "integer", - "description": "number of discord integrations to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter discord integrations containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of discord integrations to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.DiscordsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Store a discord integration for the authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["DiscordIntegration"], - "summary": "Store discord integration", - "parameters": [ - { - "description": "Payload of the discord integration request", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.DiscordStore" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/responses.DiscordResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/discord-integrations/{discordID}": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update a discord integration for the currently authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["DiscordIntegration"], - "summary": "Update a discord integration", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the discord integration", - "name": "discordID", - "in": "path", - "required": true - }, - { - "description": "Payload of discord integration to update", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.DiscordUpdate" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.DiscordResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a discord integration for a user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Webhooks"], - "summary": "Delete discord integration", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the discord integration", - "name": "discordID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/discord/event": { - "post": { - "description": "Publish a discord event to the registered listeners", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Discord"], - "summary": "Consume a discord event", - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/heartbeats": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get the last time a phone number requested for outstanding messages. It will be sorted by timestamp in descending order.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Heartbeats"], - "summary": "Get heartbeats of an owner phone number", - "parameters": [ - { - "type": "string", - "default": "+18005550199", - "description": "the owner's phone number", - "name": "owner", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "description": "number of heartbeats to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of heartbeats to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.HeartbeatsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Store the heartbeat to make notify that a phone number is still active", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Heartbeats"], - "summary": "Register heartbeat of an owner phone number", - "parameters": [ - { - "description": "Payload of the heartbeat request", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.HeartbeatStore" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.HeartbeatResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/integration/3cx/messages": { - "post": { - "description": "Sends an SMS message from the 3CX platform", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["3CXIntegration"], - "summary": "Sends a 3CX SMS message", - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/message-threads": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get list of contacts which a phone number has communicated with (threads). It will be sorted by timestamp in descending order.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["MessageThreads"], - "summary": "Get message threads for a phone number", - "parameters": [ - { - "type": "string", - "default": "+18005550199", - "description": "owner phone number", - "name": "owner", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "description": "number of messages to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter message threads containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of messages to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageThreadsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/message-threads/{messageThreadID}": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Updates the details of a message thread", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["MessageThreads"], - "summary": "Update a message thread", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the message thread", - "name": "messageThreadID", - "in": "path", - "required": true - }, - { - "description": "Payload of message thread details to update", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageThreadUpdate" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a message thread from the database and also deletes all the messages in the thread.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["MessageThreads"], - "summary": "Delete a message thread from the database.", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the message thread", - "name": "messageThreadID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get list of messages which are sent between 2 phone numbers. It will be sorted by timestamp in descending order.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Get messages which are sent between 2 phone numbers", - "parameters": [ - { - "type": "string", - "default": "+18005550199", - "description": "the owner's phone number", - "name": "owner", - "in": "query", - "required": true - }, - { - "type": "string", - "default": "+18005550100", - "description": "the contact's phone number", - "name": "contact", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "description": "number of messages to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter messages containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of messages to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessagesResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/bulk-send": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Add bulk SMS messages to be sent by the android phone", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Send bulk SMS messages", - "parameters": [ - { - "description": "Bulk send message request payload", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageBulkSend" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/responses.MessagesResponse" - } - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/calls/missed": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "This endpoint is called by the httpSMS android app to register a missed call event on the mobile phone.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Register a missed call event on the mobile phone", - "parameters": [ - { - "description": "Payload of the missed call event.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageCallMissed" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/outstanding": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get an outstanding message to be sent by an android phone", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Get an outstanding message", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703cb", - "description": "The ID of the message", - "name": "message_id", - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/receive": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Add a new message received from a mobile phone", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Receive a new SMS message from a mobile phone", - "parameters": [ - { - "description": "Received message request payload", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageReceive" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/search": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "This returns the list of all messages based on the filter criteria including missed calls", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Search all messages of a user", - "parameters": [ - { - "type": "string", - "description": "Cloudflare turnstile token https://www.cloudflare.com/en-gb/application-services/products/turnstile/", - "name": "token", - "in": "header", - "required": true - }, - { - "type": "string", - "default": "+18005550199,+18005550100", - "description": "the owner's phone numbers", - "name": "owners", - "in": "query", - "required": true - }, - { - "minimum": 0, - "type": "integer", - "description": "number of messages to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter messages containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 200, - "minimum": 1, - "type": "integer", - "description": "number of messages to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessagesResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/send": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Add a new SMS message to be sent by your Android phone", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Send an SMS message", - "parameters": [ - { - "description": "Send message request payload", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageSend" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/{messageID}": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get a message from the database by the message ID.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Get a message from the database.", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the message", - "name": "messageID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a message from the database and removes the message content from the list of threads.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Delete a message from the database.", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the message", - "name": "messageID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/messages/{messageID}/events": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Use this endpoint to send events for a message when it is failed, sent or delivered by the mobile phone.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Messages"], - "summary": "Upsert an event for a message on the mobile phone", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the message", - "name": "messageID", - "in": "path", - "required": true - }, - { - "description": "Payload of the event emitted.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageEvent" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phone-api-keys": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get list phone API keys which a user has registered on the httpSMS application", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["PhoneAPIKeys"], - "summary": "Get the phone API keys of a user", - "parameters": [ - { - "minimum": 0, - "type": "integer", - "description": "number of phone api keys to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter phone api keys with name containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 100, - "minimum": 1, - "type": "integer", - "description": "number of phone api keys to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneAPIKeysResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Creates a new phone API key which can be used to log in to the httpSMS app on your Android phone", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["PhoneAPIKeys"], - "summary": "Store phone API key", - "parameters": [ - { - "description": "Payload of new phone API key.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.PhoneAPIKeyStoreRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneAPIKeyResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phone-api-keys/{phoneAPIKeyID}": { - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a phone API Key from the database and cannot be used for authentication anymore.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["PhoneAPIKeys"], - "summary": "Delete a phone API key from the database.", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the phone API key", - "name": "phoneAPIKeyID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phone-api-keys/{phoneAPIKeyID}/phones/{phoneID}": { - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "You will need to login again to the httpSMS app on your Android phone with a new phone API key.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["PhoneAPIKeys"], - "summary": "Remove the association of a phone from the phone API key.", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the phone API key", - "name": "phoneAPIKeyID", - "in": "path", - "required": true - }, - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the phone", - "name": "phoneID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phones": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get list of phones which a user has registered on the http sms application", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Phones"], - "summary": "Get phones of a user", - "parameters": [ - { - "minimum": 0, - "type": "integer", - "description": "number of heartbeats to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter phones containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of phones to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhonesResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Updates properties of a user's phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Phones"], - "summary": "Upsert Phone", - "parameters": [ - { - "description": "Payload of new phone number.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.PhoneUpsert" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phones/fcm-token": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Updates the FCM token of a phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Phones"], - "summary": "Upserts the FCM token of a phone", - "parameters": [ - { - "description": "Payload of new FCM token.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.PhoneFCMToken" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/phones/{phoneID}": { - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a phone that has been sored in the database", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Phones"], - "summary": "Delete Phone", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the phone", - "name": "phoneID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/send-schedules": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "List all send schedules owned by the authenticated user.", - "produces": ["application/json"], - "tags": ["SendSchedules"], - "summary": "List send schedules", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageSendSchedulesResponse" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Create a new send schedule for the authenticated user.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["SendSchedules"], - "summary": "Create send schedule", - "parameters": [ - { - "description": "Payload of new send schedule.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageSendScheduleStore" - } - } - ], - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/responses.MessageSendScheduleResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "402": { - "description": "Payment Required", - "schema": { - "$ref": "#/definitions/responses.PaymentRequired" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/send-schedules/{scheduleID}": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update a send schedule owned by the authenticated user.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["SendSchedules"], - "summary": "Update send schedule", - "parameters": [ - { - "type": "string", - "description": "Schedule ID", - "name": "scheduleID", - "in": "path", - "required": true - }, - { - "description": "Payload of updated send schedule.", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.MessageSendScheduleStore" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.MessageSendScheduleResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a send schedule owned by the authenticated user.", - "produces": ["application/json"], - "tags": ["SendSchedules"], - "summary": "Delete send schedule", - "parameters": [ - { - "type": "string", - "description": "Schedule ID", - "name": "scheduleID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content" - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/me": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get details of the currently authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Get current user", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.UserResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Updates the details of the currently authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Update a user", - "parameters": [ - { - "description": "Payload of user details to update", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.UserUpdate" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.PhoneResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Deletes the currently authenticated user together with all their data.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Delete a user", - "responses": { - "201": { - "description": "Created", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/subscription": { - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Cancel the subscription of the authenticated user.", - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Cancel the user's subscription", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/subscription-update-url": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Fetches the subscription URL of the authenticated user.", - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Currently authenticated user subscription update URL", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.OkString" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/subscription/invoices/{subscriptionInvoiceID}": { - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Generates a new invoice PDF file for the given subscription payment with given parameters.", - "consumes": ["application/json"], - "produces": ["application/pdf"], - "tags": ["Users"], - "summary": "Generate a subscription payment invoice", - "parameters": [ - { - "description": "Generate subscription payment invoice parameters", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.UserPaymentInvoice" - } - }, - { - "type": "string", - "description": "ID of the subscription invoice to generate the PDF for", - "name": "subscriptionInvoiceID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "file" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/subscription/payments": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Subscription payments are generated throughout the lifecycle of a subscription, typically there is one at the time of purchase and then one for each renewal.", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Get the last 10 subscription payments.", - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.UserSubscriptionPaymentsResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/{userID}/api-keys": { - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Rotate the user's API key in case the current API Key is compromised", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Rotate the user's API Key", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the user to update", - "name": "userID", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.UserResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/users/{userID}/notifications": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update the email notification settings for a user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Users"], - "summary": "Update notification settings", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the user to update", - "name": "userID", - "in": "path", - "required": true - }, - { - "description": "User notification details to update", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.UserNotificationUpdate" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.UserResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/v1/attachments/{userID}/{messageID}/{attachmentIndex}/{filename}": { - "get": { - "description": "Download an MMS attachment by its path components", - "produces": ["application/octet-stream"], - "tags": ["Attachments"], - "summary": "Download a message attachment", - "parameters": [ - { - "type": "string", - "description": "User ID", - "name": "userID", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Message ID", - "name": "messageID", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Attachment index", - "name": "attachmentIndex", - "in": "path", - "required": true - }, - { - "type": "string", - "description": "Filename with extension", - "name": "filename", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "type": "file" - } - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/responses.NotFound" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/webhooks": { - "get": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Get the webhooks of a user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Webhooks"], - "summary": "Get webhooks of a user", - "parameters": [ - { - "minimum": 0, - "type": "integer", - "description": "number of webhooks to skip", - "name": "skip", - "in": "query" - }, - { - "type": "string", - "description": "filter webhooks containing query", - "name": "query", - "in": "query" - }, - { - "maximum": 20, - "minimum": 1, - "type": "integer", - "description": "number of webhooks to return", - "name": "limit", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.WebhooksResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "post": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Store a webhook for the authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Webhooks"], - "summary": "Store a webhook", - "parameters": [ - { - "description": "Payload of the webhook request", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.WebhookStore" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.WebhookResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - }, - "/webhooks/{webhookID}": { - "put": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Update a webhook for the currently authenticated user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Webhooks"], - "summary": "Update a webhook", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the webhook", - "name": "webhookID", - "in": "path", - "required": true - }, - { - "description": "Payload of webhook details to update", - "name": "payload", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/requests.WebhookUpdate" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/responses.WebhookResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - }, - "delete": { - "security": [ - { - "ApiKeyAuth": [] - } - ], - "description": "Delete a webhook for a user", - "consumes": ["application/json"], - "produces": ["application/json"], - "tags": ["Webhooks"], - "summary": "Delete webhook", - "parameters": [ - { - "type": "string", - "default": "32343a19-da5e-4b1b-a767-3298a73703ca", - "description": "ID of the webhook", - "name": "webhookID", - "in": "path", - "required": true - } - ], - "responses": { - "204": { - "description": "No Content", - "schema": { - "$ref": "#/definitions/responses.NoContent" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/responses.BadRequest" - } - }, - "401": { - "description": "Unauthorized", - "schema": { - "$ref": "#/definitions/responses.Unauthorized" - } - }, - "422": { - "description": "Unprocessable Entity", - "schema": { - "$ref": "#/definitions/responses.UnprocessableEntity" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/responses.InternalServerError" - } - } - } - } - } - }, - "definitions": { - "entities.BillingUsage": { - "type": "object", - "required": [ - "created_at", - "end_timestamp", - "id", - "received_messages", - "sent_messages", - "start_timestamp", - "total_cost", - "updated_at", - "user_id" - ], - "properties": { - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "end_timestamp": { - "type": "string", - "example": "2022-01-31T23:59:59+00:00" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "received_messages": { - "type": "integer", - "example": 465 - }, - "sent_messages": { - "type": "integer", - "example": 321 - }, - "start_timestamp": { - "type": "string", - "example": "2022-01-01T00:00:00+00:00" - }, - "total_cost": { - "type": "integer", - "example": 0 - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.Discord": { - "type": "object", - "required": [ - "created_at", - "id", - "incoming_channel_id", - "name", - "server_id", - "updated_at", - "user_id" - ], - "properties": { - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "incoming_channel_id": { - "type": "string", - "example": "1095780203256627291" - }, - "name": { - "type": "string", - "example": "Game Server" - }, - "server_id": { - "type": "string", - "example": "1095778291488653372" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.Heartbeat": { - "type": "object", - "required": [ - "charging", - "id", - "owner", - "timestamp", - "user_id", - "version" - ], - "properties": { - "charging": { - "type": "boolean", - "example": true - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "owner": { - "type": "string", - "example": "+18005550199" - }, - "timestamp": { - "type": "string", - "example": "2022-06-05T14:26:01.520828+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - }, - "version": { - "type": "string", - "example": "344c10f" - } - } - }, - "entities.Message": { - "type": "object", - "required": [ - "attachments", - "contact", - "content", - "created_at", - "encrypted", - "id", - "max_send_attempts", - "order_timestamp", - "owner", - "request_received_at", - "send_attempt_count", - "sim", - "status", - "type", - "updated_at", - "user_id" - ], - "properties": { - "attachments": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "https://example.com/image.jpg", - "https://example.com/video.mp4" - ] - }, - "contact": { - "type": "string", - "example": "+18005550100" - }, - "content": { - "type": "string", - "example": "This is a sample text message" - }, - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "delivered_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "encrypted": { - "type": "boolean", - "example": false - }, - "expired_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "failed_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "failure_reason": { - "type": "string", - "example": "UNKNOWN" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "last_attempted_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "max_send_attempts": { - "type": "integer", - "example": 1 - }, - "order_timestamp": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "owner": { - "type": "string", - "example": "+18005550199" - }, - "received_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "request_id": { - "type": "string", - "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" - }, - "request_received_at": { - "type": "string", - "example": "2022-06-05T14:26:01.520828+03:00" - }, - "scheduled_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "scheduled_send_time": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "send_attempt_count": { - "type": "integer", - "example": 0 - }, - "send_time": { - "description": "SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message", - "type": "integer", - "example": 133414 - }, - "sent_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "sim": { - "description": "SIM is the SIM card to use to send the message\n* SMS1: use the SIM card in slot 1\n* SMS2: use the SIM card in slot 2\n* DEFAULT: used the default communication SIM card", - "allOf": [ - { - "$ref": "#/definitions/entities.SIM" - } - ], - "example": "DEFAULT" - }, - "status": { - "type": "string", - "example": "pending" - }, - "type": { - "type": "string", - "example": "mobile-terminated" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.MessageSendSchedule": { - "type": "object", - "required": [ - "created_at", - "id", - "name", - "timezone", - "updated_at", - "user_id", - "windows" - ], - "properties": { - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "name": { - "type": "string", - "example": "Business Hours" - }, - "timezone": { - "type": "string", - "example": "Europe/Tallinn" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - }, - "windows": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.MessageSendScheduleWindow" - } - } - } - }, - "entities.MessageSendScheduleWindow": { - "type": "object", - "required": ["day_of_week", "end_minute", "start_minute"], - "properties": { - "day_of_week": { - "type": "integer", - "example": 1 - }, - "end_minute": { - "type": "integer", - "example": 1020 - }, - "start_minute": { - "type": "integer", - "example": 540 - } - } - }, - "entities.MessageThread": { - "type": "object", - "required": [ - "color", - "contact", - "created_at", - "id", - "is_archived", - "last_message_content", - "last_message_id", - "order_timestamp", - "owner", - "status", - "updated_at", - "user_id" - ], - "properties": { - "color": { - "type": "string", - "example": "indigo" - }, + "schemes": [ + "https" + ], + "swagger": "2.0", + "info": { + "description": "Use your Android phone to send and receive SMS messages via a simple programmable API with end-to-end encryption.", + "title": "httpSMS API Reference", "contact": { - "type": "string", - "example": "+18005550100" - }, - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703ca" - }, - "is_archived": { - "type": "boolean", - "example": false - }, - "last_message_content": { - "type": "string", - "example": "This is a sample message content" - }, - "last_message_id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703ca" - }, - "order_timestamp": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "owner": { - "type": "string", - "example": "+18005550199" - }, - "status": { - "type": "string", - "example": "PENDING" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.Phone": { - "type": "object", - "required": [ - "created_at", - "id", - "max_send_attempts", - "message_expiration_seconds", - "messages_per_minute", - "phone_number", - "sim", - "updated_at", - "user_id" - ], - "properties": { - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "fcm_token": { - "type": "string", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "max_send_attempts": { - "description": "MaxSendAttempts determines how many times to retry sending an SMS message", - "type": "integer", - "example": 2 - }, - "message_expiration_seconds": { - "description": "MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired.", - "type": "integer" - }, - "message_send_schedule_id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "messages_per_minute": { - "type": "integer", - "example": 1 - }, - "missed_call_auto_reply": { - "type": "string", - "example": "This phone cannot receive calls. Please send an SMS instead." - }, - "phone_number": { - "type": "string", - "example": "+18005550199" - }, - "sim": { - "$ref": "#/definitions/entities.SIM" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.PhoneAPIKey": { - "type": "object", - "required": [ - "api_key", - "created_at", - "id", - "name", - "phone_ids", - "phone_numbers", - "updated_at", - "user_email", - "user_id" - ], - "properties": { - "api_key": { - "type": "string", - "example": "pk_DGW8NwQp7mxKaSZ72Xq9v6xxxxx" - }, - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "name": { - "type": "string", - "example": "Business Phone Key" - }, - "phone_ids": { - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "32343a19-da5e-4b1b-a767-3298a73703cb", - "32343a19-da5e-4b1b-a767-3298a73703cc" - ] - }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550199", "+18005550100"] - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" + "name": "support@httpsms.com", + "email": "support@httpsms.com" }, - "user_email": { - "type": "string", - "example": "user@gmail.com" + "license": { + "name": "AGPL-3.0", + "url": "https://raw.githubusercontent.com/NdoleStudio/http-sms-manager/main/LICENSE" }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "entities.SIM": { - "type": "string", - "enum": ["SIM1", "SIM2"], - "x-enum-varnames": ["SIM1", "SIM2"] + "version": "1.0" }, - "entities.SubscriptionName": { - "type": "string", - "enum": [ - "free", - "pro-monthly", - "pro-yearly", - "ultra-monthly", - "ultra-yearly", - "pro-lifetime", - "20k-monthly", - "100k-monthly", - "50k-monthly", - "200k-monthly", - "20k-yearly" - ], - "x-enum-varnames": [ - "SubscriptionNameFree", - "SubscriptionNameProMonthly", - "SubscriptionNameProYearly", - "SubscriptionNameUltraMonthly", - "SubscriptionNameUltraYearly", - "SubscriptionNameProLifetime", - "SubscriptionName20KMonthly", - "SubscriptionName100KMonthly", - "SubscriptionName50KMonthly", - "SubscriptionName200KMonthly", - "SubscriptionName20KYearly" - ] - }, - "entities.User": { - "type": "object", - "required": [ - "api_key", - "created_at", - "email", - "id", - "notification_heartbeat_enabled", - "notification_message_status_enabled", - "notification_newsletter_enabled", - "notification_webhook_enabled", - "subscription_id", - "subscription_name", - "timezone", - "updated_at" - ], - "properties": { - "active_phone_id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "api_key": { - "type": "string", - "example": "x-api-key" - }, - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "email": { - "type": "string", - "example": "name@email.com" - }, - "id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - }, - "notification_heartbeat_enabled": { - "type": "boolean", - "example": true - }, - "notification_message_status_enabled": { - "type": "boolean", - "example": true - }, - "notification_newsletter_enabled": { - "type": "boolean", - "example": true - }, - "notification_webhook_enabled": { - "type": "boolean", - "example": true - }, - "subscription_ends_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "subscription_id": { - "type": "string", - "example": "8f9c71b8-b84e-4417-8408-a62274f65a08" - }, - "subscription_name": { - "allOf": [ - { - "$ref": "#/definitions/entities.SubscriptionName" + "host": "api.httpsms.com", + "basePath": "/v1", + "paths": { + "/billing/usage": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the summary of sent and received messages for a user in the current month", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Billing" + ], + "summary": "Get Billing Usage.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.BillingUsageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } } - ], - "example": "free" - }, - "subscription_renews_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "subscription_status": { - "type": "string", - "example": "on_trial" - }, - "timezone": { - "type": "string", - "example": "Europe/Helsinki" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - } - } - }, - "entities.Webhook": { - "type": "object", - "required": [ - "created_at", - "events", - "id", - "phone_numbers", - "signing_key", - "updated_at", - "url", - "user_id" - ], - "properties": { - "created_at": { - "type": "string", - "example": "2022-06-05T14:26:02.302718+03:00" - }, - "events": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["message.phone.received"] - }, - "id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" - }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550199", "+18005550100"] - }, - "signing_key": { - "type": "string", - "example": "DGW8NwQp7mxKaSZ72Xq9v67SLqSbWQvckzzmK8D6rvd7NywSEkdMJtuxKyEkYnCY" - }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" - }, - "url": { - "type": "string", - "example": "https://example.com" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "requests.DiscordStore": { - "type": "object", - "required": ["incoming_channel_id", "name", "server_id"], - "properties": { - "incoming_channel_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "server_id": { - "type": "string" - } - } - }, - "requests.DiscordUpdate": { - "type": "object", - "required": ["incoming_channel_id", "name", "server_id"], - "properties": { - "incoming_channel_id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "server_id": { - "type": "string" - } - } - }, - "requests.HeartbeatStore": { - "type": "object", - "required": ["charging", "phone_numbers"], - "properties": { - "charging": { - "type": "boolean" - }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "requests.MessageAttachment": { - "type": "object", - "required": ["content", "content_type", "name"], - "properties": { - "content": { - "description": "Content is the base64-encoded attachment data", - "type": "string", - "example": "base64data..." - }, - "content_type": { - "description": "ContentType is the MIME type of the attachment", - "type": "string", - "example": "image/jpeg" - }, - "name": { - "description": "Name is the original filename of the attachment", - "type": "string", - "example": "photo.jpg" - } - } - }, - "requests.MessageBulkSend": { - "type": "object", - "required": ["content", "from", "to"], - "properties": { - "attachments": { - "description": "Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS", - "type": "array", - "items": { - "type": "string" - } - }, - "content": { - "type": "string", - "example": "This is a sample text message" }, - "encrypted": { - "description": "Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", - "type": "boolean", - "example": false - }, - "from": { - "type": "string", - "example": "+18005550199" + "/billing/usage-history": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get billing usage records of sent and received messages for a user in the past. It will be sorted by timestamp in descending order.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Billing" + ], + "summary": "Get billing usage history.", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "number of heartbeats to skip", + "name": "skip", + "in": "query" + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "description": "number of heartbeats to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.BillingUsagesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "request_id": { - "description": "RequestID is an optional parameter used to track a request from the client's perspective", - "type": "string", - "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" + "/bulk-messages": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Fetches the last 10 bulk message order summaries for the authenticated user showing counts per status.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "BulkSMS" + ], + "summary": "List bulk message orders", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.BulkMessagesResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Sends bulk SMS messages to multiple users based on our [CSV template](https://httpsms.com/templates/httpsms-bulk.csv) or our [Excel template](https://httpsms.com/templates/httpsms-bulk.xlsx).", + "consumes": [ + "multipart/form-data" + ], + "produces": [ + "application/json" + ], + "tags": [ + "BulkSMS" + ], + "summary": "Store bulk SMS file", + "parameters": [ + { + "type": "file", + "description": "The Excel or CSV file containing the messages to be sent.", + "name": "document", + "in": "formData", + "required": true + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "to": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] - } - } - }, - "requests.MessageCallMissed": { - "type": "object", - "required": ["from", "sim", "timestamp", "to"], - "properties": { - "from": { - "type": "string", - "example": "+18005550199" + "/discord-integrations": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the discord integrations of a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DiscordIntegration" + ], + "summary": "Get discord integrations of a user", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "number of discord integrations to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter discord integrations containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of discord integrations to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.DiscordsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Store a discord integration for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DiscordIntegration" + ], + "summary": "Store discord integration", + "parameters": [ + { + "description": "Payload of the discord integration request", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.DiscordStore" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/responses.DiscordResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "sim": { - "type": "string", - "example": "SIM1" + "/discord-integrations/{discordID}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a discord integration for the currently authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DiscordIntegration" + ], + "summary": "Update a discord integration", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the discord integration", + "name": "discordID", + "in": "path", + "required": true + }, + { + "description": "Payload of discord integration to update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.DiscordUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.DiscordResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a discord integration for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Webhooks" + ], + "summary": "Delete discord integration", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the discord integration", + "name": "discordID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "timestamp": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" + "/discord/event": { + "post": { + "description": "Publish a discord event to the registered listeners", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discord" + ], + "summary": "Consume a discord event", + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "to": { - "type": "string", - "example": "+18005550100" - } - } - }, - "requests.MessageEvent": { - "type": "object", - "required": ["event_name", "reason", "timestamp"], - "properties": { - "event_name": { - "description": "EventName is the type of event\n* SENT: is emitted when a message is sent by the mobile phone\n* FAILED: is event is emitted when the message could not be sent by the mobile phone\n* DELIVERED: is event is emitted when a delivery report has been received by the mobile phone", - "type": "string", - "example": "SENT" + "/heartbeats": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the last time a phone number requested for outstanding messages. It will be sorted by timestamp in descending order.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Heartbeats" + ], + "summary": "Get heartbeats of an owner phone number", + "parameters": [ + { + "type": "string", + "default": "+18005550199", + "description": "the owner's phone number", + "name": "owner", + "in": "query", + "required": true + }, + { + "minimum": 0, + "type": "integer", + "description": "number of heartbeats to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of heartbeats to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.HeartbeatsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Store the heartbeat to make notify that a phone number is still active", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Heartbeats" + ], + "summary": "Register heartbeat of an owner phone number", + "parameters": [ + { + "description": "Payload of the heartbeat request", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.HeartbeatStore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.HeartbeatResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "reason": { - "description": "Reason is the exact error message in case the event is an error", - "type": "string" + "/integration/3cx/messages": { + "post": { + "description": "Sends an SMS message from the 3CX platform", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "3CXIntegration" + ], + "summary": "Sends a 3CX SMS message", + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "timestamp": { - "description": "Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible", - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" - } - } - }, - "requests.MessageReceive": { - "type": "object", - "required": ["content", "encrypted", "from", "sim", "timestamp", "to"], - "properties": { - "attachments": { - "description": "Attachments is the list of MMS attachments received with the message", - "type": "array", - "items": { - "$ref": "#/definitions/requests.MessageAttachment" - } + "/message-threads": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get list of contacts which a phone number has communicated with (threads). It will be sorted by timestamp in descending order.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "MessageThreads" + ], + "summary": "Get message threads for a phone number", + "parameters": [ + { + "type": "string", + "default": "+18005550199", + "description": "owner phone number", + "name": "owner", + "in": "query", + "required": true + }, + { + "minimum": 0, + "type": "integer", + "description": "number of messages to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter message threads containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of messages to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageThreadsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "content": { - "type": "string", - "example": "This is a sample text message received on a phone" + "/message-threads/{messageThreadID}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates the details of a message thread", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "MessageThreads" + ], + "summary": "Update a message thread", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the message thread", + "name": "messageThreadID", + "in": "path", + "required": true + }, + { + "description": "Payload of message thread details to update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageThreadUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a message thread from the database and also deletes all the messages in the thread.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "MessageThreads" + ], + "summary": "Delete a message thread from the database.", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the message thread", + "name": "messageThreadID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "encrypted": { - "description": "Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", - "type": "boolean", - "example": false + "/messages": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get list of messages which are sent between 2 phone numbers. It will be sorted by timestamp in descending order.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Get messages which are sent between 2 phone numbers", + "parameters": [ + { + "type": "string", + "default": "+18005550199", + "description": "the owner's phone number", + "name": "owner", + "in": "query", + "required": true + }, + { + "type": "string", + "default": "+18005550100", + "description": "the contact's phone number", + "name": "contact", + "in": "query", + "required": true + }, + { + "minimum": 0, + "type": "integer", + "description": "number of messages to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter messages containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of messages to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessagesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "from": { - "type": "string", - "example": "+18005550199" + "/messages/bulk-send": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add bulk SMS messages to be sent by the android phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Send bulk SMS messages", + "parameters": [ + { + "description": "Bulk send message request payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageBulkSend" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/responses.MessagesResponse" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "sim": { - "description": "SIM card that received the message", - "allOf": [ - { - "$ref": "#/definitions/entities.SIM" + "/messages/calls/missed": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "This endpoint is called by the httpSMS android app to register a missed call event on the mobile phone.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Register a missed call event on the mobile phone", + "parameters": [ + { + "description": "Payload of the missed call event.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageCallMissed" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } } - ], - "example": "SIM1" }, - "timestamp": { - "description": "Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible", - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" + "/messages/outstanding": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get an outstanding message to be sent by an android phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Get an outstanding message", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703cb", + "description": "The ID of the message", + "name": "message_id", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "to": { - "type": "string", - "example": "+18005550100" - } - } - }, - "requests.MessageSend": { - "type": "object", - "required": ["content", "from", "to"], - "properties": { - "attachments": { - "description": "Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS", - "type": "array", - "items": { - "type": "string" - }, - "example": [ - "https://example.com/image.jpg", - "https://example.com/video.mp4" - ] + "/messages/receive": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a new message received from a mobile phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Receive a new SMS message from a mobile phone", + "parameters": [ + { + "description": "Received message request payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageReceive" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "content": { - "type": "string", - "example": "This is a sample text message" + "/messages/search": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "This returns the list of all messages based on the filter criteria including missed calls", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Search all messages of a user", + "parameters": [ + { + "type": "string", + "description": "Cloudflare turnstile token https://www.cloudflare.com/en-gb/application-services/products/turnstile/", + "name": "token", + "in": "header", + "required": true + }, + { + "type": "string", + "default": "+18005550199,+18005550100", + "description": "the owner's phone numbers", + "name": "owners", + "in": "query", + "required": true + }, + { + "minimum": 0, + "type": "integer", + "description": "number of messages to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter messages containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 200, + "minimum": 1, + "type": "integer", + "description": "number of messages to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessagesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "encrypted": { - "description": "Encrypted is an optional parameter used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", - "type": "boolean", - "example": false + "/messages/send": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Add a new SMS message to be sent by your Android phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Send an SMS message", + "parameters": [ + { + "description": "Send message request payload", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageSend" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "from": { - "type": "string", - "example": "+18005550199" + "/messages/{messageID}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get a message from the database by the message ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Get a message from the database.", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the message", + "name": "messageID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a message from the database and removes the message content from the list of threads.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Delete a message from the database.", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the message", + "name": "messageID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "request_id": { - "description": "RequestID is an optional parameter used to track a request from the client's perspective", - "type": "string", - "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" + "/messages/{messageID}/events": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Use this endpoint to send events for a message when it is failed, sent or delivered by the mobile phone.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Messages" + ], + "summary": "Upsert an event for a message on the mobile phone", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the message", + "name": "messageID", + "in": "path", + "required": true + }, + { + "description": "Payload of the event emitted.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageEvent" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "send_at": { - "description": "SendAt is an optional parameter used to schedule a message to be sent in the future. The time is considered to be in your profile's local timezone and you can queue messages for up to 20 days (480 hours) in the future.", - "type": "string", - "example": "2025-12-19T16:39:57-08:00" + "/phone-api-keys": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get list phone API keys which a user has registered on the httpSMS application", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "PhoneAPIKeys" + ], + "summary": "Get the phone API keys of a user", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "number of phone api keys to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter phone api keys with name containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 100, + "minimum": 1, + "type": "integer", + "description": "number of phone api keys to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneAPIKeysResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Creates a new phone API key which can be used to log in to the httpSMS app on your Android phone", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "PhoneAPIKeys" + ], + "summary": "Store phone API key", + "parameters": [ + { + "description": "Payload of new phone API key.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.PhoneAPIKeyStoreRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneAPIKeyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "402": { + "description": "Payment Required", + "schema": { + "$ref": "#/definitions/responses.PaymentRequired" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "to": { - "type": "string", - "example": "+18005550100" - } - } - }, - "requests.MessageSendScheduleStore": { - "type": "object", - "required": ["name", "timezone", "windows"], - "properties": { - "name": { - "type": "string" + "/phone-api-keys/{phoneAPIKeyID}": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a phone API Key from the database and cannot be used for authentication anymore.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "PhoneAPIKeys" + ], + "summary": "Delete a phone API key from the database.", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the phone API key", + "name": "phoneAPIKeyID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "timezone": { - "type": "string" + "/phone-api-keys/{phoneAPIKeyID}/phones/{phoneID}": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "You will need to login again to the httpSMS app on your Android phone with a new phone API key.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "PhoneAPIKeys" + ], + "summary": "Remove the association of a phone from the phone API key.", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the phone API key", + "name": "phoneAPIKeyID", + "in": "path", + "required": true + }, + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the phone", + "name": "phoneID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "windows": { - "type": "array", - "items": { - "$ref": "#/definitions/requests.MessageSendScheduleWindow" - } - } - } - }, - "requests.MessageSendScheduleWindow": { - "type": "object", - "required": ["day_of_week", "end_minute", "start_minute"], - "properties": { - "day_of_week": { - "type": "integer" + "/phones": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get list of phones which a user has registered on the http sms application", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Phones" + ], + "summary": "Get phones of a user", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "number of heartbeats to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter phones containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of phones to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhonesResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates properties of a user's phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Phones" + ], + "summary": "Upsert Phone", + "parameters": [ + { + "description": "Payload of new phone number.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.PhoneUpsert" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "end_minute": { - "type": "integer" + "/phones/fcm-token": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates the FCM token of a phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Phones" + ], + "summary": "Upserts the FCM token of a phone", + "parameters": [ + { + "description": "Payload of new FCM token.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.PhoneFCMToken" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "start_minute": { - "type": "integer" - } - } - }, - "requests.MessageThreadUpdate": { - "type": "object", - "required": ["is_archived"], - "properties": { - "is_archived": { - "type": "boolean", - "example": true - } - } - }, - "requests.PhoneAPIKeyStoreRequest": { - "type": "object", - "required": ["name"], - "properties": { - "name": { - "type": "string", - "example": "My Phone API Key" - } - } - }, - "requests.PhoneFCMToken": { - "type": "object", - "required": ["fcm_token", "phone_number", "sim"], - "properties": { - "fcm_token": { - "type": "string", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + "/phones/{phoneID}": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a phone that has been sored in the database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Phones" + ], + "summary": "Delete Phone", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the phone", + "name": "phoneID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "phone_number": { - "type": "string", - "example": "[+18005550199]" + "/send-schedules": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List all send schedules owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "SendSchedules" + ], + "summary": "List send schedules", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageSendSchedulesResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Create a new send schedule for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SendSchedules" + ], + "summary": "Create send schedule", + "parameters": [ + { + "description": "Payload of new send schedule.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageSendScheduleStore" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/responses.MessageSendScheduleResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "402": { + "description": "Payment Required", + "schema": { + "$ref": "#/definitions/responses.PaymentRequired" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "sim": { - "description": "SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot", - "type": "string", - "example": "SIM1" - } - } - }, - "requests.PhoneUpsert": { - "type": "object", - "required": [ - "fcm_token", - "max_send_attempts", - "message_expiration_seconds", - "messages_per_minute", - "missed_call_auto_reply", - "phone_number", - "sim" - ], - "properties": { - "fcm_token": { - "type": "string", - "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + "/send-schedules/{scheduleID}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a send schedule owned by the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "SendSchedules" + ], + "summary": "Update send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "in": "path", + "required": true + }, + { + "description": "Payload of updated send schedule.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.MessageSendScheduleStore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageSendScheduleResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a send schedule owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "SendSchedules" + ], + "summary": "Delete send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "max_send_attempts": { - "description": "MaxSendAttempts is the number of attempts when sending an SMS message to handle the case where the phone is offline.", - "type": "integer", - "example": 2 + "/users/me": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get details of the currently authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates the details of the currently authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update a user", + "parameters": [ + { + "description": "Payload of user details to update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UserUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.PhoneResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Deletes the currently authenticated user together with all their data.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Delete a user", + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "message_expiration_seconds": { - "description": "MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired.", - "type": "integer", - "example": 12345 + "/users/subscription": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Cancel the subscription of the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Cancel the user's subscription", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "message_send_schedule_id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + "/users/subscription-update-url": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Fetches the subscription URL of the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Currently authenticated user subscription update URL", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.OkString" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "messages_per_minute": { - "type": "integer", - "example": 1 + "/users/subscription/invoices/{subscriptionInvoiceID}": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Generates a new invoice PDF file for the given subscription payment with given parameters.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/pdf" + ], + "tags": [ + "Users" + ], + "summary": "Generate a subscription payment invoice", + "parameters": [ + { + "description": "Generate subscription payment invoice parameters", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UserPaymentInvoice" + } + }, + { + "type": "string", + "description": "ID of the subscription invoice to generate the PDF for", + "name": "subscriptionInvoiceID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "missed_call_auto_reply": { - "type": "string", - "example": "e.g. This phone cannot receive calls. Please send an SMS instead." + "/users/subscription/payments": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Subscription payments are generated throughout the lifecycle of a subscription, typically there is one at the time of purchase and then one for each renewal.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Get the last 10 subscription payments.", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.UserSubscriptionPaymentsResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "phone_number": { - "type": "string", - "example": "+18005550199" + "/users/{userID}/api-keys": { + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Rotate the user's API key in case the current API Key is compromised", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Rotate the user's API Key", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the user to update", + "name": "userID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "sim": { - "description": "SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot", - "type": "string", - "example": "SIM1" - } - } - }, - "requests.UserNotificationUpdate": { - "type": "object", - "required": [ - "heartbeat_enabled", - "message_status_enabled", - "newsletter_enabled", - "webhook_enabled" - ], - "properties": { - "heartbeat_enabled": { - "type": "boolean", - "example": true + "/users/{userID}/notifications": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update the email notification settings for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Users" + ], + "summary": "Update notification settings", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the user to update", + "name": "userID", + "in": "path", + "required": true + }, + { + "description": "User notification details to update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.UserNotificationUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.UserResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "message_status_enabled": { - "type": "boolean", - "example": true + "/v1/attachments/{userID}/{messageID}/{attachmentIndex}/{filename}": { + "get": { + "description": "Download an MMS attachment by its path components", + "produces": [ + "application/octet-stream" + ], + "tags": [ + "Attachments" + ], + "summary": "Download a message attachment", + "parameters": [ + { + "type": "string", + "description": "User ID", + "name": "userID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Message ID", + "name": "messageID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Attachment index", + "name": "attachmentIndex", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Filename with extension", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "newsletter_enabled": { - "type": "boolean", - "example": true + "/webhooks": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Get the webhooks of a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Webhooks" + ], + "summary": "Get webhooks of a user", + "parameters": [ + { + "minimum": 0, + "type": "integer", + "description": "number of webhooks to skip", + "name": "skip", + "in": "query" + }, + { + "type": "string", + "description": "filter webhooks containing query", + "name": "query", + "in": "query" + }, + { + "maximum": 20, + "minimum": 1, + "type": "integer", + "description": "number of webhooks to return", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.WebhooksResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Store a webhook for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Webhooks" + ], + "summary": "Store a webhook", + "parameters": [ + { + "description": "Payload of the webhook request", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.WebhookStore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.WebhookResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } }, - "webhook_enabled": { - "type": "boolean", - "example": true + "/webhooks/{webhookID}": { + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Update a webhook for the currently authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Webhooks" + ], + "summary": "Update a webhook", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the webhook", + "name": "webhookID", + "in": "path", + "required": true + }, + { + "description": "Payload of webhook details to update", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.WebhookUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.WebhookResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "delete": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Delete a webhook for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Webhooks" + ], + "summary": "Delete webhook", + "parameters": [ + { + "type": "string", + "default": "32343a19-da5e-4b1b-a767-3298a73703ca", + "description": "ID of the webhook", + "name": "webhookID", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content", + "schema": { + "$ref": "#/definitions/responses.NoContent" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.BadRequest" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } } - } }, - "requests.UserPaymentInvoice": { - "type": "object", - "required": [ - "address", - "city", - "country", - "name", - "notes", - "state", - "zip_code" - ], - "properties": { - "address": { - "type": "string", - "example": "221B Baker Street, London" - }, - "city": { - "type": "string", - "example": "Los Angeles" - }, - "country": { - "type": "string", - "example": "US" + "definitions": { + "entities.BillingUsage": { + "type": "object", + "required": [ + "created_at", + "end_timestamp", + "id", + "received_messages", + "sent_messages", + "start_timestamp", + "total_cost", + "updated_at", + "user_id" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "end_timestamp": { + "type": "string", + "example": "2022-01-31T23:59:59+00:00" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "received_messages": { + "type": "integer", + "example": 465 + }, + "sent_messages": { + "type": "integer", + "example": 321 + }, + "start_timestamp": { + "type": "string", + "example": "2022-01-01T00:00:00+00:00" + }, + "total_cost": { + "type": "integer", + "example": 0 + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "name": { - "type": "string", - "example": "Acme Corp" + "entities.BulkMessage": { + "type": "object", + "required": [ + "created_at", + "delivered_count", + "failed_count", + "pending_count", + "request_id", + "scheduled_count", + "sent_count", + "total" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "delivered_count": { + "type": "integer", + "example": 25 + }, + "failed_count": { + "type": "integer", + "example": 5 + }, + "pending_count": { + "type": "integer", + "example": 30 + }, + "request_id": { + "type": "string", + "example": "bulk-32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "scheduled_count": { + "type": "integer", + "example": 50 + }, + "sent_count": { + "type": "integer", + "example": 40 + }, + "total": { + "type": "integer", + "example": 150 + } + } }, - "notes": { - "type": "string", - "example": "Thank you for your business!" + "entities.Discord": { + "type": "object", + "required": [ + "created_at", + "id", + "incoming_channel_id", + "name", + "server_id", + "updated_at", + "user_id" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "incoming_channel_id": { + "type": "string", + "example": "1095780203256627291" + }, + "name": { + "type": "string", + "example": "Game Server" + }, + "server_id": { + "type": "string", + "example": "1095778291488653372" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "state": { - "type": "string", - "example": "CA" + "entities.Heartbeat": { + "type": "object", + "required": [ + "charging", + "id", + "owner", + "timestamp", + "user_id", + "version" + ], + "properties": { + "charging": { + "type": "boolean", + "example": true + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "owner": { + "type": "string", + "example": "+18005550199" + }, + "timestamp": { + "type": "string", + "example": "2022-06-05T14:26:01.520828+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + }, + "version": { + "type": "string", + "example": "344c10f" + } + } }, - "zip_code": { - "type": "string", - "example": "9800" - } - } - }, - "requests.UserUpdate": { - "type": "object", - "required": ["active_phone_id", "timezone"], - "properties": { - "active_phone_id": { - "type": "string", - "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + "entities.Message": { + "type": "object", + "required": [ + "attachments", + "contact", + "content", + "created_at", + "encrypted", + "id", + "max_send_attempts", + "order_timestamp", + "owner", + "request_received_at", + "send_attempt_count", + "sim", + "status", + "type", + "updated_at", + "user_id" + ], + "properties": { + "attachments": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://example.com/image.jpg", + "https://example.com/video.mp4" + ] + }, + "contact": { + "type": "string", + "example": "+18005550100" + }, + "content": { + "type": "string", + "example": "This is a sample text message" + }, + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "delivered_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "encrypted": { + "type": "boolean", + "example": false + }, + "expired_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "failed_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "failure_reason": { + "type": "string", + "example": "UNKNOWN" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "last_attempted_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "max_send_attempts": { + "type": "integer", + "example": 1 + }, + "order_timestamp": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "owner": { + "type": "string", + "example": "+18005550199" + }, + "received_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "request_id": { + "type": "string", + "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" + }, + "request_received_at": { + "type": "string", + "example": "2022-06-05T14:26:01.520828+03:00" + }, + "scheduled_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "scheduled_send_time": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "send_attempt_count": { + "type": "integer", + "example": 0 + }, + "send_time": { + "description": "SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message", + "type": "integer", + "example": 133414 + }, + "sent_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "sim": { + "description": "SIM is the SIM card to use to send the message\n* SMS1: use the SIM card in slot 1\n* SMS2: use the SIM card in slot 2\n* DEFAULT: used the default communication SIM card", + "allOf": [ + { + "$ref": "#/definitions/entities.SIM" + } + ], + "example": "DEFAULT" + }, + "status": { + "type": "string", + "example": "pending" + }, + "type": { + "type": "string", + "example": "mobile-terminated" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "timezone": { - "type": "string", - "example": "Europe/Helsinki" - } - } - }, - "requests.WebhookStore": { - "type": "object", - "required": ["events", "phone_numbers", "signing_key", "url"], - "properties": { - "events": { - "type": "array", - "items": { - "type": "string" - } + "entities.MessageSendSchedule": { + "type": "object", + "required": [ + "created_at", + "id", + "name", + "timezone", + "updated_at", + "user_id", + "windows" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "name": { + "type": "string", + "example": "Business Hours" + }, + "timezone": { + "type": "string", + "example": "Europe/Tallinn" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + }, + "windows": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.MessageSendScheduleWindow" + } + } + } }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] + "entities.MessageSendScheduleWindow": { + "type": "object", + "required": [ + "day_of_week", + "end_minute", + "start_minute" + ], + "properties": { + "day_of_week": { + "type": "integer", + "example": 1 + }, + "end_minute": { + "type": "integer", + "example": 1020 + }, + "start_minute": { + "type": "integer", + "example": 540 + } + } }, - "signing_key": { - "type": "string" + "entities.MessageThread": { + "type": "object", + "required": [ + "color", + "contact", + "created_at", + "id", + "is_archived", + "last_message_content", + "last_message_id", + "order_timestamp", + "owner", + "status", + "updated_at", + "user_id" + ], + "properties": { + "color": { + "type": "string", + "example": "indigo" + }, + "contact": { + "type": "string", + "example": "+18005550100" + }, + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703ca" + }, + "is_archived": { + "type": "boolean", + "example": false + }, + "last_message_content": { + "type": "string", + "example": "This is a sample message content" + }, + "last_message_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703ca" + }, + "order_timestamp": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "owner": { + "type": "string", + "example": "+18005550199" + }, + "status": { + "type": "string", + "example": "PENDING" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "url": { - "type": "string" - } - } - }, - "requests.WebhookUpdate": { - "type": "object", - "required": ["events", "phone_numbers", "signing_key", "url"], - "properties": { - "events": { - "type": "array", - "items": { - "type": "string" - } + "entities.Phone": { + "type": "object", + "required": [ + "created_at", + "id", + "max_send_attempts", + "message_expiration_seconds", + "messages_per_minute", + "phone_number", + "sim", + "updated_at", + "user_id" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "fcm_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "max_send_attempts": { + "description": "MaxSendAttempts determines how many times to retry sending an SMS message", + "type": "integer", + "example": 2 + }, + "message_expiration_seconds": { + "description": "MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired.", + "type": "integer" + }, + "message_send_schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "messages_per_minute": { + "type": "integer", + "example": 1 + }, + "missed_call_auto_reply": { + "type": "string", + "example": "This phone cannot receive calls. Please send an SMS instead." + }, + "phone_number": { + "type": "string", + "example": "+18005550199" + }, + "sim": { + "$ref": "#/definitions/entities.SIM" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] + "entities.PhoneAPIKey": { + "type": "object", + "required": [ + "api_key", + "created_at", + "id", + "name", + "phone_ids", + "phone_numbers", + "updated_at", + "user_email", + "user_id" + ], + "properties": { + "api_key": { + "type": "string", + "example": "pk_DGW8NwQp7mxKaSZ72Xq9v6xxxxx" + }, + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "name": { + "type": "string", + "example": "Business Phone Key" + }, + "phone_ids": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "32343a19-da5e-4b1b-a767-3298a73703cb", + "32343a19-da5e-4b1b-a767-3298a73703cc" + ] + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "+18005550199", + "+18005550100" + ] + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "user_email": { + "type": "string", + "example": "user@gmail.com" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "signing_key": { - "type": "string" + "entities.SIM": { + "type": "string", + "enum": [ + "SIM1", + "SIM2" + ], + "x-enum-varnames": [ + "SIM1", + "SIM2" + ] + }, + "entities.SubscriptionName": { + "type": "string", + "enum": [ + "free", + "pro-monthly", + "pro-yearly", + "ultra-monthly", + "ultra-yearly", + "pro-lifetime", + "20k-monthly", + "100k-monthly", + "50k-monthly", + "200k-monthly", + "20k-yearly" + ], + "x-enum-varnames": [ + "SubscriptionNameFree", + "SubscriptionNameProMonthly", + "SubscriptionNameProYearly", + "SubscriptionNameUltraMonthly", + "SubscriptionNameUltraYearly", + "SubscriptionNameProLifetime", + "SubscriptionName20KMonthly", + "SubscriptionName100KMonthly", + "SubscriptionName50KMonthly", + "SubscriptionName200KMonthly", + "SubscriptionName20KYearly" + ] + }, + "entities.User": { + "type": "object", + "required": [ + "api_key", + "created_at", + "email", + "id", + "notification_heartbeat_enabled", + "notification_message_status_enabled", + "notification_newsletter_enabled", + "notification_webhook_enabled", + "subscription_id", + "subscription_name", + "timezone", + "updated_at" + ], + "properties": { + "active_phone_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "api_key": { + "type": "string", + "example": "x-api-key" + }, + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "email": { + "type": "string", + "example": "name@email.com" + }, + "id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + }, + "notification_heartbeat_enabled": { + "type": "boolean", + "example": true + }, + "notification_message_status_enabled": { + "type": "boolean", + "example": true + }, + "notification_newsletter_enabled": { + "type": "boolean", + "example": true + }, + "notification_webhook_enabled": { + "type": "boolean", + "example": true + }, + "subscription_ends_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "subscription_id": { + "type": "string", + "example": "8f9c71b8-b84e-4417-8408-a62274f65a08" + }, + "subscription_name": { + "allOf": [ + { + "$ref": "#/definitions/entities.SubscriptionName" + } + ], + "example": "free" + }, + "subscription_renews_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "subscription_status": { + "type": "string", + "example": "on_trial" + }, + "timezone": { + "type": "string", + "example": "Europe/Helsinki" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + } + } }, - "url": { - "type": "string" - } - } - }, - "responses.BadRequest": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "string", - "example": "The request body is not a valid JSON string" + "entities.Webhook": { + "type": "object", + "required": [ + "created_at", + "events", + "id", + "phone_numbers", + "signing_key", + "updated_at", + "url", + "user_id" + ], + "properties": { + "created_at": { + "type": "string", + "example": "2022-06-05T14:26:02.302718+03:00" + }, + "events": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "message.phone.received" + ] + }, + "id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "+18005550199", + "+18005550100" + ] + }, + "signing_key": { + "type": "string", + "example": "DGW8NwQp7mxKaSZ72Xq9v67SLqSbWQvckzzmK8D6rvd7NywSEkdMJtuxKyEkYnCY" + }, + "updated_at": { + "type": "string", + "example": "2022-06-05T14:26:10.303278+03:00" + }, + "url": { + "type": "string", + "example": "https://example.com" + }, + "user_id": { + "type": "string", + "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" + } + } }, - "message": { - "type": "string", - "example": "The request isn't properly formed" + "requests.DiscordStore": { + "type": "object", + "required": [ + "incoming_channel_id", + "name", + "server_id" + ], + "properties": { + "incoming_channel_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "server_id": { + "type": "string" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.BillingUsageResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.BillingUsage" + "requests.DiscordUpdate": { + "type": "object", + "required": [ + "incoming_channel_id", + "name", + "server_id" + ], + "properties": { + "incoming_channel_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "server_id": { + "type": "string" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.HeartbeatStore": { + "type": "object", + "required": [ + "charging", + "phone_numbers" + ], + "properties": { + "charging": { + "type": "boolean" + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.BillingUsagesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.BillingUsage" - } + "requests.MessageAttachment": { + "type": "object", + "required": [ + "content", + "content_type", + "name" + ], + "properties": { + "content": { + "description": "Content is the base64-encoded attachment data", + "type": "string", + "example": "base64data..." + }, + "content_type": { + "description": "ContentType is the MIME type of the attachment", + "type": "string", + "example": "image/jpeg" + }, + "name": { + "description": "Name is the original filename of the attachment", + "type": "string", + "example": "photo.jpg" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageBulkSend": { + "type": "object", + "required": [ + "content", + "from", + "to" + ], + "properties": { + "attachments": { + "description": "Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS", + "type": "array", + "items": { + "type": "string" + } + }, + "content": { + "type": "string", + "example": "This is a sample text message" + }, + "encrypted": { + "description": "Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", + "type": "boolean", + "example": false + }, + "from": { + "type": "string", + "example": "+18005550199" + }, + "request_id": { + "description": "RequestID is an optional parameter used to track a request from the client's perspective", + "type": "string", + "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" + }, + "to": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "+18005550100", + "+18005550100" + ] + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.DiscordResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Discord" + "requests.MessageCallMissed": { + "type": "object", + "required": [ + "from", + "sim", + "timestamp", + "to" + ], + "properties": { + "from": { + "type": "string", + "example": "+18005550199" + }, + "sim": { + "type": "string", + "example": "SIM1" + }, + "timestamp": { + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "to": { + "type": "string", + "example": "+18005550100" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageEvent": { + "type": "object", + "required": [ + "event_name", + "reason", + "timestamp" + ], + "properties": { + "event_name": { + "description": "EventName is the type of event\n* SENT: is emitted when a message is sent by the mobile phone\n* FAILED: is event is emitted when the message could not be sent by the mobile phone\n* DELIVERED: is event is emitted when a delivery report has been received by the mobile phone", + "type": "string", + "example": "SENT" + }, + "reason": { + "description": "Reason is the exact error message in case the event is an error", + "type": "string" + }, + "timestamp": { + "description": "Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible", + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.DiscordsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Discord" - } + "requests.MessageReceive": { + "type": "object", + "required": [ + "content", + "encrypted", + "from", + "sim", + "timestamp", + "to" + ], + "properties": { + "attachments": { + "description": "Attachments is the list of MMS attachments received with the message", + "type": "array", + "items": { + "$ref": "#/definitions/requests.MessageAttachment" + } + }, + "content": { + "type": "string", + "example": "This is a sample text message received on a phone" + }, + "encrypted": { + "description": "Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", + "type": "boolean", + "example": false + }, + "from": { + "type": "string", + "example": "+18005550199" + }, + "sim": { + "description": "SIM card that received the message", + "allOf": [ + { + "$ref": "#/definitions/entities.SIM" + } + ], + "example": "SIM1" + }, + "timestamp": { + "description": "Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible", + "type": "string", + "example": "2022-06-05T14:26:09.527976+03:00" + }, + "to": { + "type": "string", + "example": "+18005550100" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageSend": { + "type": "object", + "required": [ + "content", + "from", + "to" + ], + "properties": { + "attachments": { + "description": "Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://example.com/image.jpg", + "https://example.com/video.mp4" + ] + }, + "content": { + "type": "string", + "example": "This is a sample text message" + }, + "encrypted": { + "description": "Encrypted is an optional parameter used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app", + "type": "boolean", + "example": false + }, + "from": { + "type": "string", + "example": "+18005550199" + }, + "request_id": { + "description": "RequestID is an optional parameter used to track a request from the client's perspective", + "type": "string", + "example": "153554b5-ae44-44a0-8f4f-7bbac5657ad4" + }, + "send_at": { + "description": "SendAt is an optional parameter used to schedule a message to be sent in the future. The time is considered to be in your profile's local timezone and you can queue messages for up to 20 days (480 hours) in the future.", + "type": "string", + "example": "2025-12-19T16:39:57-08:00" + }, + "to": { + "type": "string", + "example": "+18005550100" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.HeartbeatResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Heartbeat" + "requests.MessageSendScheduleStore": { + "type": "object", + "required": [ + "name", + "timezone", + "windows" + ], + "properties": { + "name": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "windows": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.MessageSendScheduleWindow" + } + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageSendScheduleWindow": { + "type": "object", + "required": [ + "day_of_week", + "end_minute", + "start_minute" + ], + "properties": { + "day_of_week": { + "type": "integer" + }, + "end_minute": { + "type": "integer" + }, + "start_minute": { + "type": "integer" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.HeartbeatsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Heartbeat" - } + "requests.MessageThreadUpdate": { + "type": "object", + "required": [ + "is_archived" + ], + "properties": { + "is_archived": { + "type": "boolean", + "example": true + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.PhoneAPIKeyStoreRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "example": "My Phone API Key" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.InternalServerError": { - "type": "object", - "required": ["message", "status"], - "properties": { - "message": { - "type": "string", - "example": "We ran into an internal error while handling the request." + "requests.PhoneFCMToken": { + "type": "object", + "required": [ + "fcm_token", + "phone_number", + "sim" + ], + "properties": { + "fcm_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + }, + "phone_number": { + "type": "string", + "example": "[+18005550199]" + }, + "sim": { + "description": "SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot", + "type": "string", + "example": "SIM1" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.MessageResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Message" + "requests.PhoneUpsert": { + "type": "object", + "required": [ + "fcm_token", + "max_send_attempts", + "message_expiration_seconds", + "messages_per_minute", + "missed_call_auto_reply", + "phone_number", + "sim" + ], + "properties": { + "fcm_token": { + "type": "string", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + }, + "max_send_attempts": { + "description": "MaxSendAttempts is the number of attempts when sending an SMS message to handle the case where the phone is offline.", + "type": "integer", + "example": 2 + }, + "message_expiration_seconds": { + "description": "MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired.", + "type": "integer", + "example": 12345 + }, + "message_send_schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "messages_per_minute": { + "type": "integer", + "example": 1 + }, + "missed_call_auto_reply": { + "type": "string", + "example": "e.g. This phone cannot receive calls. Please send an SMS instead." + }, + "phone_number": { + "type": "string", + "example": "+18005550199" + }, + "sim": { + "description": "SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot", + "type": "string", + "example": "SIM1" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.UserNotificationUpdate": { + "type": "object", + "required": [ + "heartbeat_enabled", + "message_status_enabled", + "newsletter_enabled", + "webhook_enabled" + ], + "properties": { + "heartbeat_enabled": { + "type": "boolean", + "example": true + }, + "message_status_enabled": { + "type": "boolean", + "example": true + }, + "newsletter_enabled": { + "type": "boolean", + "example": true + }, + "webhook_enabled": { + "type": "boolean", + "example": true + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessageSendScheduleResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.MessageSendSchedule" + "requests.UserPaymentInvoice": { + "type": "object", + "required": [ + "address", + "city", + "country", + "name", + "notes", + "state", + "zip_code" + ], + "properties": { + "address": { + "type": "string", + "example": "221B Baker Street, London" + }, + "city": { + "type": "string", + "example": "Los Angeles" + }, + "country": { + "type": "string", + "example": "US" + }, + "name": { + "type": "string", + "example": "Acme Corp" + }, + "notes": { + "type": "string", + "example": "Thank you for your business!" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip_code": { + "type": "string", + "example": "9800" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.UserUpdate": { + "type": "object", + "required": [ + "active_phone_id", + "timezone" + ], + "properties": { + "active_phone_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "timezone": { + "type": "string", + "example": "Europe/Helsinki" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessageSendSchedulesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.MessageSendSchedule" - } + "requests.WebhookStore": { + "type": "object", + "required": [ + "events", + "phone_numbers", + "signing_key", + "url" + ], + "properties": { + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "+18005550100", + "+18005550100" + ] + }, + "signing_key": { + "type": "string" + }, + "url": { + "type": "string" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.WebhookUpdate": { + "type": "object", + "required": [ + "events", + "phone_numbers", + "signing_key", + "url" + ], + "properties": { + "events": { + "type": "array", + "items": { + "type": "string" + } + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "+18005550100", + "+18005550100" + ] + }, + "signing_key": { + "type": "string" + }, + "url": { + "type": "string" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessageThreadsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.MessageThread" - } + "responses.BadRequest": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "string", + "example": "The request body is not a valid JSON string" + }, + "message": { + "type": "string", + "example": "The request isn't properly formed" + }, + "status": { + "type": "string", + "example": "error" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.BillingUsageResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.BillingUsage" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessagesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Message" - } + "responses.BillingUsagesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.BillingUsage" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.BulkMessagesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.BulkMessage" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.NoContent": { - "type": "object", - "required": ["message", "status"], - "properties": { - "message": { - "type": "string", - "example": "action performed successfully" + "responses.DiscordResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.Discord" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.NotFound": { - "type": "object", - "required": ["message", "status"], - "properties": { - "message": { - "type": "string", - "example": "cannot find message with ID [32343a19-da5e-4b1b-a767-3298a73703ca]" + "responses.DiscordsResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Discord" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.OkString": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "string" + "responses.HeartbeatResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.Heartbeat" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.HeartbeatsResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Heartbeat" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PaymentRequired": { - "type": "object", - "required": ["message", "status"], - "properties": { - "message": { - "type": "string", - "example": "You have reached the maximum number of allowed resources. Please upgrade your plan." + "responses.InternalServerError": { + "type": "object", + "required": [ + "message", + "status" + ], + "properties": { + "message": { + "type": "string", + "example": "We ran into an internal error while handling the request." + }, + "status": { + "type": "string", + "example": "error" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.PhoneAPIKeyResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.PhoneAPIKey" + "responses.MessageResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.Message" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.MessageSendScheduleResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.MessageSendSchedule" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhoneAPIKeysResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.PhoneAPIKey" - } + "responses.MessageSendSchedulesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.MessageSendSchedule" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.MessageThreadsResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.MessageThread" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhoneResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Phone" + "responses.MessagesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Message" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.NoContent": { + "type": "object", + "required": [ + "message", + "status" + ], + "properties": { + "message": { + "type": "string", + "example": "action performed successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhonesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Phone" - } + "responses.NotFound": { + "type": "object", + "required": [ + "message", + "status" + ], + "properties": { + "message": { + "type": "string", + "example": "cannot find message with ID [32343a19-da5e-4b1b-a767-3298a73703ca]" + }, + "status": { + "type": "string", + "example": "error" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.OkString": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "string" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.Unauthorized": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "string", - "example": "Make sure your API key is set in the [X-API-Key] header in the request" + "responses.PaymentRequired": { + "type": "object", + "required": [ + "message", + "status" + ], + "properties": { + "message": { + "type": "string", + "example": "You have reached the maximum number of allowed resources. Please upgrade your plan." + }, + "status": { + "type": "string", + "example": "error" + } + } }, - "message": { - "type": "string", - "example": "You are not authorized to carry out this request." + "responses.PhoneAPIKeyResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.PhoneAPIKey" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.UnprocessableEntity": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" + "responses.PhoneAPIKeysResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.PhoneAPIKey" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } } - } }, - "message": { - "type": "string", - "example": "validation errors while handling request" + "responses.PhoneResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.Phone" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.UserResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.User" + "responses.PhonesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Phone" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.Unauthorized": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "string", + "example": "Make sure your API key is set in the [X-API-Key] header in the request" + }, + "message": { + "type": "string", + "example": "You are not authorized to carry out this request." + }, + "status": { + "type": "string", + "example": "error" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.UserSubscriptionPaymentsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { + "responses.UnprocessableEntity": { "type": "object", - "required": ["attributes", "id", "type"], + "required": [ + "data", + "message", + "status" + ], "properties": { - "attributes": { - "type": "object", - "required": [ - "billing_reason", - "card_brand", - "card_last_four", - "created_at", - "currency", - "currency_rate", - "discount_total", - "discount_total_formatted", - "discount_total_usd", - "refunded", - "refunded_amount", - "refunded_amount_formatted", - "refunded_amount_usd", - "refunded_at", - "status", - "status_formatted", - "subtotal", - "subtotal_formatted", - "subtotal_usd", - "tax", - "tax_formatted", - "tax_inclusive", - "tax_usd", - "total", - "total_formatted", - "total_usd", - "updated_at" - ], - "properties": { - "billing_reason": { - "type": "string" - }, - "card_brand": { - "type": "string" - }, - "card_last_four": { - "type": "string" - }, - "created_at": { - "type": "string" - }, - "currency": { - "type": "string" - }, - "currency_rate": { - "type": "string" - }, - "discount_total": { - "type": "integer" - }, - "discount_total_formatted": { - "type": "string" - }, - "discount_total_usd": { - "type": "integer" - }, - "refunded": { - "type": "boolean" - }, - "refunded_amount": { - "type": "integer" - }, - "refunded_amount_formatted": { - "type": "string" - }, - "refunded_amount_usd": { - "type": "integer" - }, - "refunded_at": {}, - "status": { - "type": "string" - }, - "status_formatted": { - "type": "string" - }, - "subtotal": { - "type": "integer" - }, - "subtotal_formatted": { - "type": "string" - }, - "subtotal_usd": { - "type": "integer" - }, - "tax": { - "type": "integer" - }, - "tax_formatted": { - "type": "string" - }, - "tax_inclusive": { - "type": "boolean" - }, - "tax_usd": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "total_formatted": { - "type": "string" - }, - "total_usd": { - "type": "integer" - }, - "updated_at": { - "type": "string" - } + "data": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "message": { + "type": "string", + "example": "validation errors while handling request" + }, + "status": { + "type": "string", + "example": "error" } - }, - "id": { - "type": "string" - }, - "type": { - "type": "string" - } } - } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.UserResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.User" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.WebhookResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Webhook" + "responses.UserSubscriptionPaymentsResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "attributes", + "id", + "type" + ], + "properties": { + "attributes": { + "type": "object", + "required": [ + "billing_reason", + "card_brand", + "card_last_four", + "created_at", + "currency", + "currency_rate", + "discount_total", + "discount_total_formatted", + "discount_total_usd", + "refunded", + "refunded_amount", + "refunded_amount_formatted", + "refunded_amount_usd", + "refunded_at", + "status", + "status_formatted", + "subtotal", + "subtotal_formatted", + "subtotal_usd", + "tax", + "tax_formatted", + "tax_inclusive", + "tax_usd", + "total", + "total_formatted", + "total_usd", + "updated_at" + ], + "properties": { + "billing_reason": { + "type": "string" + }, + "card_brand": { + "type": "string" + }, + "card_last_four": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "currency": { + "type": "string" + }, + "currency_rate": { + "type": "string" + }, + "discount_total": { + "type": "integer" + }, + "discount_total_formatted": { + "type": "string" + }, + "discount_total_usd": { + "type": "integer" + }, + "refunded": { + "type": "boolean" + }, + "refunded_amount": { + "type": "integer" + }, + "refunded_amount_formatted": { + "type": "string" + }, + "refunded_amount_usd": { + "type": "integer" + }, + "refunded_at": {}, + "status": { + "type": "string" + }, + "status_formatted": { + "type": "string" + }, + "subtotal": { + "type": "integer" + }, + "subtotal_formatted": { + "type": "string" + }, + "subtotal_usd": { + "type": "integer" + }, + "tax": { + "type": "integer" + }, + "tax_formatted": { + "type": "string" + }, + "tax_inclusive": { + "type": "boolean" + }, + "tax_usd": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "total_formatted": { + "type": "string" + }, + "total_usd": { + "type": "integer" + }, + "updated_at": { + "type": "string" + } + } + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + } + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "responses.WebhookResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.Webhook" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "status": { - "type": "string", - "example": "success" + "responses.WebhooksResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.Webhook" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } } - } }, - "responses.WebhooksResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Webhook" - } - }, - "message": { - "type": "string", - "example": "Request handled successfully" - }, - "status": { - "type": "string", - "example": "success" + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "x-api-Key", + "in": "header" } - } - } - }, - "securityDefinitions": { - "ApiKeyAuth": { - "type": "apiKey", - "name": "x-api-Key", - "in": "header" } - } } diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index e2f17d92..4c6d0938 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -30,15 +30,51 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - created_at - - end_timestamp - - id - - received_messages - - sent_messages - - start_timestamp - - total_cost - - updated_at - - user_id + - created_at + - end_timestamp + - id + - received_messages + - sent_messages + - start_timestamp + - total_cost + - updated_at + - user_id + type: object + entities.BulkMessage: + properties: + created_at: + example: "2022-06-05T14:26:02.302718+03:00" + type: string + delivered_count: + example: 25 + type: integer + failed_count: + example: 5 + type: integer + pending_count: + example: 30 + type: integer + request_id: + example: bulk-32343a19-da5e-4b1b-a767-3298a73703cb + type: string + scheduled_count: + example: 50 + type: integer + sent_count: + example: 40 + type: integer + total: + example: 150 + type: integer + required: + - created_at + - delivered_count + - failed_count + - pending_count + - request_id + - scheduled_count + - sent_count + - total type: object entities.Discord: properties: @@ -64,13 +100,13 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - created_at - - id - - incoming_channel_id - - name - - server_id - - updated_at - - user_id + - created_at + - id + - incoming_channel_id + - name + - server_id + - updated_at + - user_id type: object entities.Heartbeat: properties: @@ -93,19 +129,19 @@ definitions: example: 344c10f type: string required: - - charging - - id - - owner - - timestamp - - user_id - - version + - charging + - id + - owner + - timestamp + - user_id + - version type: object entities.Message: properties: attachments: example: - - https://example.com/image.jpg - - https://example.com/video.mp4 + - https://example.com/image.jpg + - https://example.com/video.mp4 items: type: string type: array @@ -167,8 +203,7 @@ definitions: example: 0 type: integer send_time: - description: - SendDuration is the number of nanoseconds from when the request + description: SendDuration is the number of nanoseconds from when the request was received until when the mobile phone send the message example: 133414 type: integer @@ -177,7 +212,7 @@ definitions: type: string sim: allOf: - - $ref: "#/definitions/entities.SIM" + - $ref: '#/definitions/entities.SIM' description: |- SIM is the SIM card to use to send the message * SMS1: use the SIM card in slot 1 @@ -197,22 +232,22 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - attachments - - contact - - content - - created_at - - encrypted - - id - - max_send_attempts - - order_timestamp - - owner - - request_received_at - - send_attempt_count - - sim - - status - - type - - updated_at - - user_id + - attachments + - contact + - content + - created_at + - encrypted + - id + - max_send_attempts + - order_timestamp + - owner + - request_received_at + - send_attempt_count + - sim + - status + - type + - updated_at + - user_id type: object entities.MessageSendSchedule: properties: @@ -236,16 +271,16 @@ definitions: type: string windows: items: - $ref: "#/definitions/entities.MessageSendScheduleWindow" + $ref: '#/definitions/entities.MessageSendScheduleWindow' type: array required: - - created_at - - id - - name - - timezone - - updated_at - - user_id - - windows + - created_at + - id + - name + - timezone + - updated_at + - user_id + - windows type: object entities.MessageSendScheduleWindow: properties: @@ -259,9 +294,9 @@ definitions: example: 540 type: integer required: - - day_of_week - - end_minute - - start_minute + - day_of_week + - end_minute + - start_minute type: object entities.MessageThread: properties: @@ -302,18 +337,18 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - color - - contact - - created_at - - id - - is_archived - - last_message_content - - last_message_id - - order_timestamp - - owner - - status - - updated_at - - user_id + - color + - contact + - created_at + - id + - is_archived + - last_message_content + - last_message_id + - order_timestamp + - owner + - status + - updated_at + - user_id type: object entities.Phone: properties: @@ -327,14 +362,12 @@ definitions: example: 32343a19-da5e-4b1b-a767-3298a73703cb type: string max_send_attempts: - description: - MaxSendAttempts determines how many times to retry sending an + description: MaxSendAttempts determines how many times to retry sending an SMS message example: 2 type: integer message_expiration_seconds: - description: - MessageExpirationSeconds is the duration in seconds after sending + description: MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired. type: integer message_send_schedule_id: @@ -350,7 +383,7 @@ definitions: example: "+18005550199" type: string sim: - $ref: "#/definitions/entities.SIM" + $ref: '#/definitions/entities.SIM' updated_at: example: "2022-06-05T14:26:10.303278+03:00" type: string @@ -358,15 +391,15 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - created_at - - id - - max_send_attempts - - message_expiration_seconds - - messages_per_minute - - phone_number - - sim - - updated_at - - user_id + - created_at + - id + - max_send_attempts + - message_expiration_seconds + - messages_per_minute + - phone_number + - sim + - updated_at + - user_id type: object entities.PhoneAPIKey: properties: @@ -384,15 +417,15 @@ definitions: type: string phone_ids: example: - - 32343a19-da5e-4b1b-a767-3298a73703cb - - 32343a19-da5e-4b1b-a767-3298a73703cc + - 32343a19-da5e-4b1b-a767-3298a73703cb + - 32343a19-da5e-4b1b-a767-3298a73703cc items: type: string type: array phone_numbers: example: - - "+18005550199" - - "+18005550100" + - "+18005550199" + - "+18005550100" items: type: string type: array @@ -406,50 +439,50 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - api_key - - created_at - - id - - name - - phone_ids - - phone_numbers - - updated_at - - user_email - - user_id + - api_key + - created_at + - id + - name + - phone_ids + - phone_numbers + - updated_at + - user_email + - user_id type: object entities.SIM: enum: - - SIM1 - - SIM2 + - SIM1 + - SIM2 type: string x-enum-varnames: - - SIM1 - - SIM2 + - SIM1 + - SIM2 entities.SubscriptionName: enum: - - free - - pro-monthly - - pro-yearly - - ultra-monthly - - ultra-yearly - - pro-lifetime - - 20k-monthly - - 100k-monthly - - 50k-monthly - - 200k-monthly - - 20k-yearly + - free + - pro-monthly + - pro-yearly + - ultra-monthly + - ultra-yearly + - pro-lifetime + - 20k-monthly + - 100k-monthly + - 50k-monthly + - 200k-monthly + - 20k-yearly type: string x-enum-varnames: - - SubscriptionNameFree - - SubscriptionNameProMonthly - - SubscriptionNameProYearly - - SubscriptionNameUltraMonthly - - SubscriptionNameUltraYearly - - SubscriptionNameProLifetime - - SubscriptionName20KMonthly - - SubscriptionName100KMonthly - - SubscriptionName50KMonthly - - SubscriptionName200KMonthly - - SubscriptionName20KYearly + - SubscriptionNameFree + - SubscriptionNameProMonthly + - SubscriptionNameProYearly + - SubscriptionNameUltraMonthly + - SubscriptionNameUltraYearly + - SubscriptionNameProLifetime + - SubscriptionName20KMonthly + - SubscriptionName100KMonthly + - SubscriptionName50KMonthly + - SubscriptionName200KMonthly + - SubscriptionName20KYearly entities.User: properties: active_phone_id: @@ -487,7 +520,7 @@ definitions: type: string subscription_name: allOf: - - $ref: "#/definitions/entities.SubscriptionName" + - $ref: '#/definitions/entities.SubscriptionName' example: free subscription_renews_at: example: "2022-06-05T14:26:02.302718+03:00" @@ -502,18 +535,18 @@ definitions: example: "2022-06-05T14:26:10.303278+03:00" type: string required: - - api_key - - created_at - - email - - id - - notification_heartbeat_enabled - - notification_message_status_enabled - - notification_newsletter_enabled - - notification_webhook_enabled - - subscription_id - - subscription_name - - timezone - - updated_at + - api_key + - created_at + - email + - id + - notification_heartbeat_enabled + - notification_message_status_enabled + - notification_newsletter_enabled + - notification_webhook_enabled + - subscription_id + - subscription_name + - timezone + - updated_at type: object entities.Webhook: properties: @@ -522,7 +555,7 @@ definitions: type: string events: example: - - message.phone.received + - message.phone.received items: type: string type: array @@ -531,8 +564,8 @@ definitions: type: string phone_numbers: example: - - "+18005550199" - - "+18005550100" + - "+18005550199" + - "+18005550100" items: type: string type: array @@ -549,14 +582,14 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - created_at - - events - - id - - phone_numbers - - signing_key - - updated_at - - url - - user_id + - created_at + - events + - id + - phone_numbers + - signing_key + - updated_at + - url + - user_id type: object requests.DiscordStore: properties: @@ -567,9 +600,9 @@ definitions: server_id: type: string required: - - incoming_channel_id - - name - - server_id + - incoming_channel_id + - name + - server_id type: object requests.DiscordUpdate: properties: @@ -580,9 +613,9 @@ definitions: server_id: type: string required: - - incoming_channel_id - - name - - server_id + - incoming_channel_id + - name + - server_id type: object requests.HeartbeatStore: properties: @@ -593,8 +626,8 @@ definitions: type: string type: array required: - - charging - - phone_numbers + - charging + - phone_numbers type: object requests.MessageAttachment: properties: @@ -611,15 +644,14 @@ definitions: example: photo.jpg type: string required: - - content - - content_type - - name + - content + - content_type + - name type: object requests.MessageBulkSend: properties: attachments: - description: - Attachments are optional. When you provide a list of attachments, + description: Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS items: type: string @@ -628,8 +660,7 @@ definitions: example: This is a sample text message type: string encrypted: - description: - Encrypted is used to determine if the content is end-to-end encrypted. + description: Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app example: false type: boolean @@ -637,22 +668,21 @@ definitions: example: "+18005550199" type: string request_id: - description: - RequestID is an optional parameter used to track a request from + description: RequestID is an optional parameter used to track a request from the client's perspective example: 153554b5-ae44-44a0-8f4f-7bbac5657ad4 type: string to: example: - - "+18005550100" - - "+18005550100" + - "+18005550100" + - "+18005550100" items: type: string type: array required: - - content - - from - - to + - content + - from + - to type: object requests.MessageCallMissed: properties: @@ -669,10 +699,10 @@ definitions: example: "+18005550100" type: string required: - - from - - sim - - timestamp - - to + - from + - sim + - timestamp + - to type: object requests.MessageEvent: properties: @@ -688,31 +718,28 @@ definitions: description: Reason is the exact error message in case the event is an error type: string timestamp: - description: - Timestamp is the time when the event was emitted, Please send + description: Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible example: "2022-06-05T14:26:09.527976+03:00" type: string required: - - event_name - - reason - - timestamp + - event_name + - reason + - timestamp type: object requests.MessageReceive: properties: attachments: - description: - Attachments is the list of MMS attachments received with the + description: Attachments is the list of MMS attachments received with the message items: - $ref: "#/definitions/requests.MessageAttachment" + $ref: '#/definitions/requests.MessageAttachment' type: array content: example: This is a sample text message received on a phone type: string encrypted: - description: - Encrypted is used to determine if the content is end-to-end encrypted. + description: Encrypted is used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app example: false type: boolean @@ -721,12 +748,11 @@ definitions: type: string sim: allOf: - - $ref: "#/definitions/entities.SIM" + - $ref: '#/definitions/entities.SIM' description: SIM card that received the message example: SIM1 timestamp: - description: - Timestamp is the time when the event was emitted, Please send + description: Timestamp is the time when the event was emitted, Please send the timestamp in UTC with as much precision as possible example: "2022-06-05T14:26:09.527976+03:00" type: string @@ -734,22 +760,21 @@ definitions: example: "+18005550100" type: string required: - - content - - encrypted - - from - - sim - - timestamp - - to + - content + - encrypted + - from + - sim + - timestamp + - to type: object requests.MessageSend: properties: attachments: - description: - Attachments are optional. When you provide a list of attachments, + description: Attachments are optional. When you provide a list of attachments, the message will be sent out as an MMS example: - - https://example.com/image.jpg - - https://example.com/video.mp4 + - https://example.com/image.jpg + - https://example.com/video.mp4 items: type: string type: array @@ -757,8 +782,7 @@ definitions: example: This is a sample text message type: string encrypted: - description: - Encrypted is an optional parameter used to determine if the content + description: Encrypted is an optional parameter used to determine if the content is end-to-end encrypted. Make sure to set the encryption key on the httpSMS mobile app example: false @@ -767,14 +791,12 @@ definitions: example: "+18005550199" type: string request_id: - description: - RequestID is an optional parameter used to track a request from + description: RequestID is an optional parameter used to track a request from the client's perspective example: 153554b5-ae44-44a0-8f4f-7bbac5657ad4 type: string send_at: - description: - SendAt is an optional parameter used to schedule a message to + description: SendAt is an optional parameter used to schedule a message to be sent in the future. The time is considered to be in your profile's local timezone and you can queue messages for up to 20 days (480 hours) in the future. @@ -784,9 +806,9 @@ definitions: example: "+18005550100" type: string required: - - content - - from - - to + - content + - from + - to type: object requests.MessageSendScheduleStore: properties: @@ -796,12 +818,12 @@ definitions: type: string windows: items: - $ref: "#/definitions/requests.MessageSendScheduleWindow" + $ref: '#/definitions/requests.MessageSendScheduleWindow' type: array required: - - name - - timezone - - windows + - name + - timezone + - windows type: object requests.MessageSendScheduleWindow: properties: @@ -812,9 +834,9 @@ definitions: start_minute: type: integer required: - - day_of_week - - end_minute - - start_minute + - day_of_week + - end_minute + - start_minute type: object requests.MessageThreadUpdate: properties: @@ -822,7 +844,7 @@ definitions: example: true type: boolean required: - - is_archived + - is_archived type: object requests.PhoneAPIKeyStoreRequest: properties: @@ -830,7 +852,7 @@ definitions: example: My Phone API Key type: string required: - - name + - name type: object requests.PhoneFCMToken: properties: @@ -838,18 +860,17 @@ definitions: example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd..... type: string phone_number: - example: "[+18005550199]" + example: '[+18005550199]' type: string sim: - description: - SIM is the SIM slot of the phone in case the phone has more than + description: SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot example: SIM1 type: string required: - - fcm_token - - phone_number - - sim + - fcm_token + - phone_number + - sim type: object requests.PhoneUpsert: properties: @@ -857,14 +878,12 @@ definitions: example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd..... type: string max_send_attempts: - description: - MaxSendAttempts is the number of attempts when sending an SMS + description: MaxSendAttempts is the number of attempts when sending an SMS message to handle the case where the phone is offline. example: 2 type: integer message_expiration_seconds: - description: - MessageExpirationSeconds is the duration in seconds after sending + description: MessageExpirationSeconds is the duration in seconds after sending a message when it is considered to be expired. example: 12345 type: integer @@ -881,19 +900,18 @@ definitions: example: "+18005550199" type: string sim: - description: - SIM is the SIM slot of the phone in case the phone has more than + description: SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot example: SIM1 type: string required: - - fcm_token - - max_send_attempts - - message_expiration_seconds - - messages_per_minute - - missed_call_auto_reply - - phone_number - - sim + - fcm_token + - max_send_attempts + - message_expiration_seconds + - messages_per_minute + - missed_call_auto_reply + - phone_number + - sim type: object requests.UserNotificationUpdate: properties: @@ -910,10 +928,10 @@ definitions: example: true type: boolean required: - - heartbeat_enabled - - message_status_enabled - - newsletter_enabled - - webhook_enabled + - heartbeat_enabled + - message_status_enabled + - newsletter_enabled + - webhook_enabled type: object requests.UserPaymentInvoice: properties: @@ -939,13 +957,13 @@ definitions: example: "9800" type: string required: - - address - - city - - country - - name - - notes - - state - - zip_code + - address + - city + - country + - name + - notes + - state + - zip_code type: object requests.UserUpdate: properties: @@ -956,8 +974,8 @@ definitions: example: Europe/Helsinki type: string required: - - active_phone_id - - timezone + - active_phone_id + - timezone type: object requests.WebhookStore: properties: @@ -967,8 +985,8 @@ definitions: type: array phone_numbers: example: - - "+18005550100" - - "+18005550100" + - "+18005550100" + - "+18005550100" items: type: string type: array @@ -977,10 +995,10 @@ definitions: url: type: string required: - - events - - phone_numbers - - signing_key - - url + - events + - phone_numbers + - signing_key + - url type: object requests.WebhookUpdate: properties: @@ -990,8 +1008,8 @@ definitions: type: array phone_numbers: example: - - "+18005550100" - - "+18005550100" + - "+18005550100" + - "+18005550100" items: type: string type: array @@ -1000,10 +1018,10 @@ definitions: url: type: string required: - - events - - phone_numbers - - signing_key - - url + - events + - phone_numbers + - signing_key + - url type: object responses.BadRequest: properties: @@ -1017,14 +1035,14 @@ definitions: example: error type: string required: - - data - - message - - status + - data + - message + - status type: object responses.BillingUsageResponse: properties: data: - $ref: "#/definitions/entities.BillingUsage" + $ref: '#/definitions/entities.BillingUsage' message: example: Request handled successfully type: string @@ -1032,15 +1050,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.BillingUsagesResponse: properties: data: items: - $ref: "#/definitions/entities.BillingUsage" + $ref: '#/definitions/entities.BillingUsage' type: array message: example: Request handled successfully @@ -1049,14 +1067,31 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status + type: object + responses.BulkMessagesResponse: + properties: + data: + items: + $ref: '#/definitions/entities.BulkMessage' + type: array + message: + example: Request handled successfully + type: string + status: + example: success + type: string + required: + - data + - message + - status type: object responses.DiscordResponse: properties: data: - $ref: "#/definitions/entities.Discord" + $ref: '#/definitions/entities.Discord' message: example: Request handled successfully type: string @@ -1064,15 +1099,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.DiscordsResponse: properties: data: items: - $ref: "#/definitions/entities.Discord" + $ref: '#/definitions/entities.Discord' type: array message: example: Request handled successfully @@ -1081,14 +1116,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.HeartbeatResponse: properties: data: - $ref: "#/definitions/entities.Heartbeat" + $ref: '#/definitions/entities.Heartbeat' message: example: Request handled successfully type: string @@ -1096,15 +1131,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.HeartbeatsResponse: properties: data: items: - $ref: "#/definitions/entities.Heartbeat" + $ref: '#/definitions/entities.Heartbeat' type: array message: example: Request handled successfully @@ -1113,9 +1148,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.InternalServerError: properties: @@ -1126,13 +1161,13 @@ definitions: example: error type: string required: - - message - - status + - message + - status type: object responses.MessageResponse: properties: data: - $ref: "#/definitions/entities.Message" + $ref: '#/definitions/entities.Message' message: example: Request handled successfully type: string @@ -1140,14 +1175,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.MessageSendScheduleResponse: properties: data: - $ref: "#/definitions/entities.MessageSendSchedule" + $ref: '#/definitions/entities.MessageSendSchedule' message: example: Request handled successfully type: string @@ -1155,15 +1190,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.MessageSendSchedulesResponse: properties: data: items: - $ref: "#/definitions/entities.MessageSendSchedule" + $ref: '#/definitions/entities.MessageSendSchedule' type: array message: example: Request handled successfully @@ -1172,15 +1207,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.MessageThreadsResponse: properties: data: items: - $ref: "#/definitions/entities.MessageThread" + $ref: '#/definitions/entities.MessageThread' type: array message: example: Request handled successfully @@ -1189,15 +1224,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.MessagesResponse: properties: data: items: - $ref: "#/definitions/entities.Message" + $ref: '#/definitions/entities.Message' type: array message: example: Request handled successfully @@ -1206,9 +1241,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.NoContent: properties: @@ -1219,8 +1254,8 @@ definitions: example: success type: string required: - - message - - status + - message + - status type: object responses.NotFound: properties: @@ -1231,8 +1266,8 @@ definitions: example: error type: string required: - - message - - status + - message + - status type: object responses.OkString: properties: @@ -1245,28 +1280,27 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.PaymentRequired: properties: message: - example: - You have reached the maximum number of allowed resources. Please + example: You have reached the maximum number of allowed resources. Please upgrade your plan. type: string status: example: error type: string required: - - message - - status + - message + - status type: object responses.PhoneAPIKeyResponse: properties: data: - $ref: "#/definitions/entities.PhoneAPIKey" + $ref: '#/definitions/entities.PhoneAPIKey' message: example: Request handled successfully type: string @@ -1274,15 +1308,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.PhoneAPIKeysResponse: properties: data: items: - $ref: "#/definitions/entities.PhoneAPIKey" + $ref: '#/definitions/entities.PhoneAPIKey' type: array message: example: Request handled successfully @@ -1291,14 +1325,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.PhoneResponse: properties: data: - $ref: "#/definitions/entities.Phone" + $ref: '#/definitions/entities.Phone' message: example: Request handled successfully type: string @@ -1306,15 +1340,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.PhonesResponse: properties: data: items: - $ref: "#/definitions/entities.Phone" + $ref: '#/definitions/entities.Phone' type: array message: example: Request handled successfully @@ -1323,9 +1357,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.Unauthorized: properties: @@ -1339,9 +1373,9 @@ definitions: example: error type: string required: - - data - - message - - status + - data + - message + - status type: object responses.UnprocessableEntity: properties: @@ -1358,14 +1392,14 @@ definitions: example: error type: string required: - - data - - message - - status + - data + - message + - status type: object responses.UserResponse: properties: data: - $ref: "#/definitions/entities.User" + $ref: '#/definitions/entities.User' message: example: Request handled successfully type: string @@ -1373,9 +1407,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.UserSubscriptionPaymentsResponse: properties: @@ -1438,42 +1472,42 @@ definitions: updated_at: type: string required: - - billing_reason - - card_brand - - card_last_four - - created_at - - currency - - currency_rate - - discount_total - - discount_total_formatted - - discount_total_usd - - refunded - - refunded_amount - - refunded_amount_formatted - - refunded_amount_usd - - refunded_at - - status - - status_formatted - - subtotal - - subtotal_formatted - - subtotal_usd - - tax - - tax_formatted - - tax_inclusive - - tax_usd - - total - - total_formatted - - total_usd - - updated_at + - billing_reason + - card_brand + - card_last_four + - created_at + - currency + - currency_rate + - discount_total + - discount_total_formatted + - discount_total_usd + - refunded + - refunded_amount + - refunded_amount_formatted + - refunded_amount_usd + - refunded_at + - status + - status_formatted + - subtotal + - subtotal_formatted + - subtotal_usd + - tax + - tax_formatted + - tax_inclusive + - tax_usd + - total + - total_formatted + - total_usd + - updated_at type: object id: type: string type: type: string required: - - attributes - - id - - type + - attributes + - id + - type type: object type: array message: @@ -1483,14 +1517,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.WebhookResponse: properties: data: - $ref: "#/definitions/entities.Webhook" + $ref: '#/definitions/entities.Webhook' message: example: Request handled successfully type: string @@ -1498,15 +1532,15 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.WebhooksResponse: properties: data: items: - $ref: "#/definitions/entities.Webhook" + $ref: '#/definitions/entities.Webhook' type: array message: example: Request handled successfully @@ -1515,17 +1549,16 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object host: api.httpsms.com info: contact: email: support@httpsms.com name: support@httpsms.com - description: - Use your Android phone to send and receive SMS messages via a simple + description: Use your Android phone to send and receive SMS messages via a simple programmable API with end-to-end encryption. license: name: AGPL-3.0 @@ -1536,1857 +1569,1865 @@ paths: /billing/usage: get: consumes: - - application/json - description: - Get the summary of sent and received messages for a user in the + - application/json + description: Get the summary of sent and received messages for a user in the current month produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.BillingUsageResponse" + $ref: '#/definitions/responses.BillingUsageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get Billing Usage. tags: - - Billing + - Billing /billing/usage-history: get: consumes: - - application/json - description: - Get billing usage records of sent and received messages for a user + - application/json + description: Get billing usage records of sent and received messages for a user in the past. It will be sorted by timestamp in descending order. parameters: - - description: number of heartbeats to skip - in: query - minimum: 0 - name: skip - type: integer - - description: number of heartbeats to return - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer + - description: number of heartbeats to skip + in: query + minimum: 0 + name: skip + type: integer + - description: number of heartbeats to return + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.BillingUsagesResponse" + $ref: '#/definitions/responses.BillingUsagesResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get billing usage history. tags: - - Billing + - Billing /bulk-messages: + get: + consumes: + - application/json + description: Fetches the last 10 bulk message order summaries for the authenticated + user showing counts per status. + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.BulkMessagesResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/responses.Unauthorized' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.InternalServerError' + security: + - ApiKeyAuth: [] + summary: List bulk message orders + tags: + - BulkSMS post: consumes: - - multipart/form-data - description: - Sends bulk SMS messages to multiple users based on our [CSV template](https://httpsms.com/templates/httpsms-bulk.csv) + - multipart/form-data + description: Sends bulk SMS messages to multiple users based on our [CSV template](https://httpsms.com/templates/httpsms-bulk.csv) or our [Excel template](https://httpsms.com/templates/httpsms-bulk.xlsx). parameters: - - description: The Excel or CSV file containing the messages to be sent. - in: formData - name: document - required: true - type: file + - description: The Excel or CSV file containing the messages to be sent. + in: formData + name: document + required: true + type: file produces: - - application/json + - application/json responses: "202": description: Accepted schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Store bulk SMS file tags: - - BulkSMS + - BulkSMS /discord-integrations: get: consumes: - - application/json + - application/json description: Get the discord integrations of a user parameters: - - description: number of discord integrations to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter discord integrations containing query - in: query - name: query - type: string - - description: number of discord integrations to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - description: number of discord integrations to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter discord integrations containing query + in: query + name: query + type: string + - description: number of discord integrations to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.DiscordsResponse" + $ref: '#/definitions/responses.DiscordsResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get discord integrations of a user tags: - - DiscordIntegration + - DiscordIntegration post: consumes: - - application/json + - application/json description: Store a discord integration for the authenticated user parameters: - - description: Payload of the discord integration request - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.DiscordStore" + - description: Payload of the discord integration request + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.DiscordStore' produces: - - application/json + - application/json responses: "201": description: Created schema: - $ref: "#/definitions/responses.DiscordResponse" + $ref: '#/definitions/responses.DiscordResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Store discord integration tags: - - DiscordIntegration + - DiscordIntegration /discord-integrations/{discordID}: delete: consumes: - - application/json + - application/json description: Delete a discord integration for a user parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the discord integration - in: path - name: discordID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the discord integration + in: path + name: discordID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete discord integration tags: - - Webhooks + - Webhooks put: consumes: - - application/json + - application/json description: Update a discord integration for the currently authenticated user parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the discord integration - in: path - name: discordID - required: true - type: string - - description: Payload of discord integration to update - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.DiscordUpdate" + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the discord integration + in: path + name: discordID + required: true + type: string + - description: Payload of discord integration to update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.DiscordUpdate' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.DiscordResponse" + $ref: '#/definitions/responses.DiscordResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update a discord integration tags: - - DiscordIntegration + - DiscordIntegration /discord/event: post: consumes: - - application/json + - application/json description: Publish a discord event to the registered listeners produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' summary: Consume a discord event tags: - - Discord + - Discord /heartbeats: get: consumes: - - application/json - description: - Get the last time a phone number requested for outstanding messages. + - application/json + description: Get the last time a phone number requested for outstanding messages. It will be sorted by timestamp in descending order. parameters: - - default: "+18005550199" - description: the owner's phone number - in: query - name: owner - required: true - type: string - - description: number of heartbeats to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter containing query - in: query - name: query - type: string - - description: number of heartbeats to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - default: "+18005550199" + description: the owner's phone number + in: query + name: owner + required: true + type: string + - description: number of heartbeats to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter containing query + in: query + name: query + type: string + - description: number of heartbeats to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.HeartbeatsResponse" + $ref: '#/definitions/responses.HeartbeatsResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get heartbeats of an owner phone number tags: - - Heartbeats + - Heartbeats post: consumes: - - application/json - description: - Store the heartbeat to make notify that a phone number is still + - application/json + description: Store the heartbeat to make notify that a phone number is still active parameters: - - description: Payload of the heartbeat request - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.HeartbeatStore" + - description: Payload of the heartbeat request + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.HeartbeatStore' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.HeartbeatResponse" + $ref: '#/definitions/responses.HeartbeatResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Register heartbeat of an owner phone number tags: - - Heartbeats + - Heartbeats /integration/3cx/messages: post: consumes: - - application/json + - application/json description: Sends an SMS message from the 3CX platform produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' summary: Sends a 3CX SMS message tags: - - 3CXIntegration + - 3CXIntegration /message-threads: get: consumes: - - application/json - description: - Get list of contacts which a phone number has communicated with + - application/json + description: Get list of contacts which a phone number has communicated with (threads). It will be sorted by timestamp in descending order. parameters: - - default: "+18005550199" - description: owner phone number - in: query - name: owner - required: true - type: string - - description: number of messages to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter message threads containing query - in: query - name: query - type: string - - description: number of messages to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - default: "+18005550199" + description: owner phone number + in: query + name: owner + required: true + type: string + - description: number of messages to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter message threads containing query + in: query + name: query + type: string + - description: number of messages to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageThreadsResponse" + $ref: '#/definitions/responses.MessageThreadsResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get message threads for a phone number tags: - - MessageThreads + - MessageThreads /message-threads/{messageThreadID}: delete: consumes: - - application/json - description: - Delete a message thread from the database and also deletes all + - application/json + description: Delete a message thread from the database and also deletes all the messages in the thread. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the message thread - in: path - name: messageThreadID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the message thread + in: path + name: messageThreadID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete a message thread from the database. tags: - - MessageThreads + - MessageThreads put: consumes: - - application/json + - application/json description: Updates the details of a message thread parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the message thread - in: path - name: messageThreadID - required: true - type: string - - description: Payload of message thread details to update - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageThreadUpdate" + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the message thread + in: path + name: messageThreadID + required: true + type: string + - description: Payload of message thread details to update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageThreadUpdate' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneResponse" + $ref: '#/definitions/responses.PhoneResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update a message thread tags: - - MessageThreads + - MessageThreads /messages: get: consumes: - - application/json - description: - Get list of messages which are sent between 2 phone numbers. It + - application/json + description: Get list of messages which are sent between 2 phone numbers. It will be sorted by timestamp in descending order. parameters: - - default: "+18005550199" - description: the owner's phone number - in: query - name: owner - required: true - type: string - - default: "+18005550100" - description: the contact's phone number - in: query - name: contact - required: true - type: string - - description: number of messages to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter messages containing query - in: query - name: query - type: string - - description: number of messages to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - default: "+18005550199" + description: the owner's phone number + in: query + name: owner + required: true + type: string + - default: "+18005550100" + description: the contact's phone number + in: query + name: contact + required: true + type: string + - description: number of messages to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter messages containing query + in: query + name: query + type: string + - description: number of messages to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessagesResponse" + $ref: '#/definitions/responses.MessagesResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get messages which are sent between 2 phone numbers tags: - - Messages + - Messages /messages/{messageID}: delete: consumes: - - application/json - description: - Delete a message from the database and removes the message content + - application/json + description: Delete a message from the database and removes the message content from the list of threads. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the message - in: path - name: messageID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the message + in: path + name: messageID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete a message from the database. tags: - - Messages + - Messages get: consumes: - - application/json + - application/json description: Get a message from the database by the message ID. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the message - in: path - name: messageID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the message + in: path + name: messageID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get a message from the database. tags: - - Messages + - Messages /messages/{messageID}/events: post: consumes: - - application/json - description: - Use this endpoint to send events for a message when it is failed, + - application/json + description: Use this endpoint to send events for a message when it is failed, sent or delivered by the mobile phone. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the message - in: path - name: messageID - required: true - type: string - - description: Payload of the event emitted. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageEvent" + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the message + in: path + name: messageID + required: true + type: string + - description: Payload of the event emitted. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageEvent' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Upsert an event for a message on the mobile phone tags: - - Messages + - Messages /messages/bulk-send: post: consumes: - - application/json + - application/json description: Add bulk SMS messages to be sent by the android phone parameters: - - description: Bulk send message request payload - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageBulkSend" + - description: Bulk send message request payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageBulkSend' produces: - - application/json + - application/json responses: "200": description: OK schema: items: - $ref: "#/definitions/responses.MessagesResponse" + $ref: '#/definitions/responses.MessagesResponse' type: array "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Send bulk SMS messages tags: - - Messages + - Messages /messages/calls/missed: post: consumes: - - application/json - description: - This endpoint is called by the httpSMS android app to register + - application/json + description: This endpoint is called by the httpSMS android app to register a missed call event on the mobile phone. parameters: - - description: Payload of the missed call event. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageCallMissed" + - description: Payload of the missed call event. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageCallMissed' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Register a missed call event on the mobile phone tags: - - Messages + - Messages /messages/outstanding: get: consumes: - - application/json + - application/json description: Get an outstanding message to be sent by an android phone parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703cb - description: The ID of the message - in: query - name: message_id - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703cb + description: The ID of the message + in: query + name: message_id + required: true + type: string produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get an outstanding message tags: - - Messages + - Messages /messages/receive: post: consumes: - - application/json + - application/json description: Add a new message received from a mobile phone parameters: - - description: Received message request payload - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageReceive" + - description: Received message request payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageReceive' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Receive a new SMS message from a mobile phone tags: - - Messages + - Messages /messages/search: get: consumes: - - application/json - description: - This returns the list of all messages based on the filter criteria + - application/json + description: This returns the list of all messages based on the filter criteria including missed calls parameters: - - description: Cloudflare turnstile token https://www.cloudflare.com/en-gb/application-services/products/turnstile/ - in: header - name: token - required: true - type: string - - default: +18005550199,+18005550100 - description: the owner's phone numbers - in: query - name: owners - required: true - type: string - - description: number of messages to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter messages containing query - in: query - name: query - type: string - - description: number of messages to return - in: query - maximum: 200 - minimum: 1 - name: limit - type: integer + - description: Cloudflare turnstile token https://www.cloudflare.com/en-gb/application-services/products/turnstile/ + in: header + name: token + required: true + type: string + - default: +18005550199,+18005550100 + description: the owner's phone numbers + in: query + name: owners + required: true + type: string + - description: number of messages to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter messages containing query + in: query + name: query + type: string + - description: number of messages to return + in: query + maximum: 200 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessagesResponse" + $ref: '#/definitions/responses.MessagesResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Search all messages of a user tags: - - Messages + - Messages /messages/send: post: consumes: - - application/json + - application/json description: Add a new SMS message to be sent by your Android phone parameters: - - description: Send message request payload - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageSend" + - description: Send message request payload + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageSend' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageResponse" + $ref: '#/definitions/responses.MessageResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Send an SMS message tags: - - Messages + - Messages /phone-api-keys: get: consumes: - - application/json - description: - Get list phone API keys which a user has registered on the httpSMS + - application/json + description: Get list phone API keys which a user has registered on the httpSMS application parameters: - - description: number of phone api keys to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter phone api keys with name containing query - in: query - name: query - type: string - - description: number of phone api keys to return - in: query - maximum: 100 - minimum: 1 - name: limit - type: integer + - description: number of phone api keys to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter phone api keys with name containing query + in: query + name: query + type: string + - description: number of phone api keys to return + in: query + maximum: 100 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneAPIKeysResponse" + $ref: '#/definitions/responses.PhoneAPIKeysResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get the phone API keys of a user tags: - - PhoneAPIKeys + - PhoneAPIKeys post: consumes: - - application/json - description: - Creates a new phone API key which can be used to log in to the + - application/json + description: Creates a new phone API key which can be used to log in to the httpSMS app on your Android phone parameters: - - description: Payload of new phone API key. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.PhoneAPIKeyStoreRequest" + - description: Payload of new phone API key. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.PhoneAPIKeyStoreRequest' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneAPIKeyResponse" + $ref: '#/definitions/responses.PhoneAPIKeyResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' + "402": + description: Payment Required + schema: + $ref: '#/definitions/responses.PaymentRequired' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Store phone API key tags: - - PhoneAPIKeys + - PhoneAPIKeys /phone-api-keys/{phoneAPIKeyID}: delete: consumes: - - application/json - description: - Delete a phone API Key from the database and cannot be used for + - application/json + description: Delete a phone API Key from the database and cannot be used for authentication anymore. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the phone API key - in: path - name: phoneAPIKeyID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the phone API key + in: path + name: phoneAPIKeyID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete a phone API key from the database. tags: - - PhoneAPIKeys + - PhoneAPIKeys /phone-api-keys/{phoneAPIKeyID}/phones/{phoneID}: delete: consumes: - - application/json - description: - You will need to login again to the httpSMS app on your Android + - application/json + description: You will need to login again to the httpSMS app on your Android phone with a new phone API key. parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the phone API key - in: path - name: phoneAPIKeyID - required: true - type: string - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the phone - in: path - name: phoneID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the phone API key + in: path + name: phoneAPIKeyID + required: true + type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the phone + in: path + name: phoneID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Remove the association of a phone from the phone API key. tags: - - PhoneAPIKeys + - PhoneAPIKeys /phones: get: consumes: - - application/json - description: - Get list of phones which a user has registered on the http sms + - application/json + description: Get list of phones which a user has registered on the http sms application parameters: - - description: number of heartbeats to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter phones containing query - in: query - name: query - type: string - - description: number of phones to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - description: number of heartbeats to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter phones containing query + in: query + name: query + type: string + - description: number of phones to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhonesResponse" + $ref: '#/definitions/responses.PhonesResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get phones of a user tags: - - Phones + - Phones put: consumes: - - application/json - description: - Updates properties of a user's phone. If the phone with this number + - application/json + description: Updates properties of a user's phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert' parameters: - - description: Payload of new phone number. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.PhoneUpsert" + - description: Payload of new phone number. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.PhoneUpsert' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneResponse" + $ref: '#/definitions/responses.PhoneResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Upsert Phone tags: - - Phones + - Phones /phones/{phoneID}: delete: consumes: - - application/json + - application/json description: Delete a phone that has been sored in the database parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the phone - in: path - name: phoneID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the phone + in: path + name: phoneID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete Phone tags: - - Phones + - Phones /phones/fcm-token: put: consumes: - - application/json - description: - Updates the FCM token of a phone. If the phone with this number + - application/json + description: Updates the FCM token of a phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert' parameters: - - description: Payload of new FCM token. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.PhoneFCMToken" + - description: Payload of new FCM token. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.PhoneFCMToken' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneResponse" + $ref: '#/definitions/responses.PhoneResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Upserts the FCM token of a phone tags: - - Phones + - Phones /send-schedules: get: description: List all send schedules owned by the authenticated user. produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageSendSchedulesResponse" + $ref: '#/definitions/responses.MessageSendSchedulesResponse' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: List send schedules tags: - - SendSchedules + - SendSchedules post: consumes: - - application/json + - application/json description: Create a new send schedule for the authenticated user. parameters: - - description: Payload of new send schedule. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageSendScheduleStore" + - description: Payload of new send schedule. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageSendScheduleStore' produces: - - application/json + - application/json responses: "201": description: Created schema: - $ref: "#/definitions/responses.MessageSendScheduleResponse" + $ref: '#/definitions/responses.MessageSendScheduleResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "402": description: Payment Required schema: - $ref: "#/definitions/responses.PaymentRequired" + $ref: '#/definitions/responses.PaymentRequired' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Create send schedule tags: - - SendSchedules + - SendSchedules /send-schedules/{scheduleID}: delete: description: Delete a send schedule owned by the authenticated user. parameters: - - description: Schedule ID - in: path - name: scheduleID - required: true - type: string + - description: Schedule ID + in: path + name: scheduleID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete send schedule tags: - - SendSchedules + - SendSchedules put: consumes: - - application/json + - application/json description: Update a send schedule owned by the authenticated user. parameters: - - description: Schedule ID - in: path - name: scheduleID - required: true - type: string - - description: Payload of updated send schedule. - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.MessageSendScheduleStore" + - description: Schedule ID + in: path + name: scheduleID + required: true + type: string + - description: Payload of updated send schedule. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.MessageSendScheduleStore' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.MessageSendScheduleResponse" + $ref: '#/definitions/responses.MessageSendScheduleResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update send schedule tags: - - SendSchedules + - SendSchedules /users/{userID}/api-keys: delete: consumes: - - application/json + - application/json description: Rotate the user's API key in case the current API Key is compromised parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the user to update - in: path - name: userID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the user to update + in: path + name: userID + required: true + type: string produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.UserResponse" + $ref: '#/definitions/responses.UserResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Rotate the user's API Key tags: - - Users + - Users /users/{userID}/notifications: put: consumes: - - application/json + - application/json description: Update the email notification settings for a user parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the user to update - in: path - name: userID - required: true - type: string - - description: User notification details to update - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.UserNotificationUpdate" + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the user to update + in: path + name: userID + required: true + type: string + - description: User notification details to update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.UserNotificationUpdate' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.UserResponse" + $ref: '#/definitions/responses.UserResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update notification settings tags: - - Users + - Users /users/me: delete: consumes: - - application/json - description: - Deletes the currently authenticated user together with all their + - application/json + description: Deletes the currently authenticated user together with all their data. produces: - - application/json + - application/json responses: "201": description: Created schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete a user tags: - - Users + - Users get: consumes: - - application/json + - application/json description: Get details of the currently authenticated user produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.UserResponse" + $ref: '#/definitions/responses.UserResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get current user tags: - - Users + - Users put: consumes: - - application/json + - application/json description: Updates the details of the currently authenticated user parameters: - - description: Payload of user details to update - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.UserUpdate" + - description: Payload of user details to update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.UserUpdate' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.PhoneResponse" + $ref: '#/definitions/responses.PhoneResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update a user tags: - - Users + - Users /users/subscription: delete: description: Cancel the subscription of the authenticated user. produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Cancel the user's subscription tags: - - Users + - Users /users/subscription-update-url: get: description: Fetches the subscription URL of the authenticated user. produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.OkString" + $ref: '#/definitions/responses.OkString' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Currently authenticated user subscription update URL tags: - - Users + - Users /users/subscription/invoices/{subscriptionInvoiceID}: post: consumes: - - application/json - description: - Generates a new invoice PDF file for the given subscription payment + - application/json + description: Generates a new invoice PDF file for the given subscription payment with given parameters. parameters: - - description: Generate subscription payment invoice parameters - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.UserPaymentInvoice" - - description: ID of the subscription invoice to generate the PDF for - in: path - name: subscriptionInvoiceID - required: true - type: string + - description: Generate subscription payment invoice parameters + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.UserPaymentInvoice' + - description: ID of the subscription invoice to generate the PDF for + in: path + name: subscriptionInvoiceID + required: true + type: string produces: - - application/pdf + - application/pdf responses: "200": description: OK @@ -3395,86 +3436,85 @@ paths: "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Generate a subscription payment invoice tags: - - Users + - Users /users/subscription/payments: get: consumes: - - application/json - description: - Subscription payments are generated throughout the lifecycle of + - application/json + description: Subscription payments are generated throughout the lifecycle of a subscription, typically there is one at the time of purchase and then one for each renewal. produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.UserSubscriptionPaymentsResponse" + $ref: '#/definitions/responses.UserSubscriptionPaymentsResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get the last 10 subscription payments. tags: - - Users + - Users /v1/attachments/{userID}/{messageID}/{attachmentIndex}/{filename}: get: description: Download an MMS attachment by its path components parameters: - - description: User ID - in: path - name: userID - required: true - type: string - - description: Message ID - in: path - name: messageID - required: true - type: string - - description: Attachment index - in: path - name: attachmentIndex - required: true - type: string - - description: Filename with extension - in: path - name: filename - required: true - type: string + - description: User ID + in: path + name: userID + required: true + type: string + - description: Message ID + in: path + name: messageID + required: true + type: string + - description: Attachment index + in: path + name: attachmentIndex + required: true + type: string + - description: Filename with extension + in: path + name: filename + required: true + type: string produces: - - application/octet-stream + - application/octet-stream responses: "200": description: OK @@ -3483,189 +3523,189 @@ paths: "404": description: Not Found schema: - $ref: "#/definitions/responses.NotFound" + $ref: '#/definitions/responses.NotFound' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' summary: Download a message attachment tags: - - Attachments + - Attachments /webhooks: get: consumes: - - application/json + - application/json description: Get the webhooks of a user parameters: - - description: number of webhooks to skip - in: query - minimum: 0 - name: skip - type: integer - - description: filter webhooks containing query - in: query - name: query - type: string - - description: number of webhooks to return - in: query - maximum: 20 - minimum: 1 - name: limit - type: integer + - description: number of webhooks to skip + in: query + minimum: 0 + name: skip + type: integer + - description: filter webhooks containing query + in: query + name: query + type: string + - description: number of webhooks to return + in: query + maximum: 20 + minimum: 1 + name: limit + type: integer produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.WebhooksResponse" + $ref: '#/definitions/responses.WebhooksResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Get webhooks of a user tags: - - Webhooks + - Webhooks post: consumes: - - application/json + - application/json description: Store a webhook for the authenticated user parameters: - - description: Payload of the webhook request - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.WebhookStore" + - description: Payload of the webhook request + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.WebhookStore' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.WebhookResponse" + $ref: '#/definitions/responses.WebhookResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Store a webhook tags: - - Webhooks + - Webhooks /webhooks/{webhookID}: delete: consumes: - - application/json + - application/json description: Delete a webhook for a user parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the webhook - in: path - name: webhookID - required: true - type: string + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the webhook + in: path + name: webhookID + required: true + type: string produces: - - application/json + - application/json responses: "204": description: No Content schema: - $ref: "#/definitions/responses.NoContent" + $ref: '#/definitions/responses.NoContent' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Delete webhook tags: - - Webhooks + - Webhooks put: consumes: - - application/json + - application/json description: Update a webhook for the currently authenticated user parameters: - - default: 32343a19-da5e-4b1b-a767-3298a73703ca - description: ID of the webhook - in: path - name: webhookID - required: true - type: string - - description: Payload of webhook details to update - in: body - name: payload - required: true - schema: - $ref: "#/definitions/requests.WebhookUpdate" + - default: 32343a19-da5e-4b1b-a767-3298a73703ca + description: ID of the webhook + in: path + name: webhookID + required: true + type: string + - description: Payload of webhook details to update + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.WebhookUpdate' produces: - - application/json + - application/json responses: "200": description: OK schema: - $ref: "#/definitions/responses.WebhookResponse" + $ref: '#/definitions/responses.WebhookResponse' "400": description: Bad Request schema: - $ref: "#/definitions/responses.BadRequest" + $ref: '#/definitions/responses.BadRequest' "401": description: Unauthorized schema: - $ref: "#/definitions/responses.Unauthorized" + $ref: '#/definitions/responses.Unauthorized' "422": description: Unprocessable Entity schema: - $ref: "#/definitions/responses.UnprocessableEntity" + $ref: '#/definitions/responses.UnprocessableEntity' "500": description: Internal Server Error schema: - $ref: "#/definitions/responses.InternalServerError" + $ref: '#/definitions/responses.InternalServerError' security: - - ApiKeyAuth: [] + - ApiKeyAuth: [] summary: Update a webhook tags: - - Webhooks + - Webhooks schemes: - - https +- https securityDefinitions: ApiKeyAuth: in: header diff --git a/api/go.mod b/api/go.mod index 1fe7dca1..754baa14 100644 --- a/api/go.mod +++ b/api/go.mod @@ -33,6 +33,7 @@ require ( github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/jszwec/csvutil v1.10.0 github.com/lib/pq v1.12.3 + github.com/matoous/go-nanoid/v2 v2.1.0 github.com/nyaruka/phonenumbers v1.7.2 github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/api/go.sum b/api/go.sum index fa1163a2..cb11e652 100644 --- a/api/go.sum +++ b/api/go.sum @@ -237,6 +237,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE= +github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= diff --git a/api/pkg/entities/bulk_message.go b/api/pkg/entities/bulk_message.go new file mode 100644 index 00000000..86227ffa --- /dev/null +++ b/api/pkg/entities/bulk_message.go @@ -0,0 +1,16 @@ +package entities + +import "time" + +// BulkMessage represents a summary of a bulk message batch +type BulkMessage struct { + RequestID string `json:"request_id" example:"bulk-csv-a1B2c3D4e5"` + Total int64 `json:"total" example:"150"` + ScheduledCount int64 `json:"scheduled_count" example:"50"` + PendingCount int64 `json:"pending_count" example:"30"` + FailedCount int64 `json:"failed_count" example:"5"` + ExpiredCount int64 `json:"expired_count" example:"3"` + SentCount int64 `json:"sent_count" example:"40"` + DeliveredCount int64 `json:"delivered_count" example:"25"` + CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"` +} diff --git a/api/pkg/handlers/bulk_message_handler.go b/api/pkg/handlers/bulk_message_handler.go index 16c833fe..c388ef14 100644 --- a/api/pkg/handlers/bulk_message_handler.go +++ b/api/pkg/handlers/bulk_message_handler.go @@ -1,18 +1,18 @@ package handlers import ( + "crypto/rand" "fmt" "sync" "sync/atomic" "github.com/NdoleStudio/httpsms/pkg/requests" - "github.com/google/uuid" - "github.com/NdoleStudio/httpsms/pkg/services" "github.com/NdoleStudio/httpsms/pkg/telemetry" "github.com/NdoleStudio/httpsms/pkg/validators" "github.com/davecgh/go-spew/spew" "github.com/gofiber/fiber/v2" + gonanoid "github.com/matoous/go-nanoid/v2" "github.com/palantir/stacktrace" ) @@ -45,9 +45,35 @@ func NewBulkMessageHandler( // RegisterRoutes registers the routes for the MessageHandler func (h *BulkMessageHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) { + router.Get("/v1/bulk-messages", h.computeRoute(middlewares, h.Index)...) router.Post("/v1/bulk-messages", h.computeRoute(middlewares, h.Store)...) } +// Index fetches the bulk message order history. +// @Summary List bulk message orders +// @Description Fetches the last 10 bulk message order summaries for the authenticated user showing counts per status. +// @Security ApiKeyAuth +// @Tags BulkSMS +// @Accept json +// @Produce json +// @Success 200 {object} responses.BulkMessagesResponse +// @Failure 401 {object} responses.Unauthorized +// @Failure 500 {object} responses.InternalServerError +// @Router /bulk-messages [get] +func (h *BulkMessageHandler) Index(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + + orders, err := h.messageService.GetBulkMessages(ctx, h.userIDFomContext(c)) + if err != nil { + msg := fmt.Sprintf("cannot fetch bulk messages for user [%s]", h.userIDFomContext(c)) + ctxLogger.Error(stacktrace.Propagate(err, msg)) + return h.responseInternalServerError(c) + } + + return h.responseOK(c, fmt.Sprintf("fetched %d bulk %s", len(orders), h.pluralize("message", len(orders))), orders) +} + // Store sends bulk SMS messages from a CSV or Excel file. // @Summary Store bulk SMS file // @Description Sends bulk SMS messages to multiple users based on our [CSV template](https://httpsms.com/templates/httpsms-bulk.csv) or our [Excel template](https://httpsms.com/templates/httpsms-bulk.xlsx). @@ -73,7 +99,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error { return h.responseBadRequest(c, err) } - messages, validationErrors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), file) + messages, fileType, validationErrors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), file) if len(validationErrors) != 0 { msg := fmt.Sprintf("validation errors [%s], while sending bulk sms from CSV file [%s] for [%s]", spew.Sdump(validationErrors), file.Filename, h.userIDFomContext(c)) ctxLogger.Warn(stacktrace.NewError(msg)) @@ -85,7 +111,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error { return h.responsePaymentRequired(c, *msg) } - requestID := uuid.New() + requestID := h.generateRequestID(fileType, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") wg := sync.WaitGroup{} count := atomic.Int64{} @@ -95,7 +121,7 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error { for _, message := range messages { wg.Add(1) var perPhoneIndex int - if message.SendTime == nil { + if message.GetSendTime() == nil { perPhoneIndex = phoneIndexCounter[message.FromPhoneNumber] phoneIndexCounter[message.FromPhoneNumber]++ } @@ -118,3 +144,22 @@ func (h *BulkMessageHandler) Store(c *fiber.Ctx) error { wg.Wait() return h.responseAccepted(c, fmt.Sprintf("Added %d out of %d messages to the queue", count.Load(), len(messages))) } + +func (h *BulkMessageHandler) generateRequestID(fileType string, alphabet string) string { + id, err := gonanoid.Generate(alphabet, 10) + if err != nil { + id = h.randomAlphaNum(10, alphabet) + } + return fmt.Sprintf("bulk-%s-%s", fileType, id) +} + +func (h *BulkMessageHandler) randomAlphaNum(length int, alphabet string) string { + b := make([]byte, length) + if _, err := rand.Read(b); err != nil { + return alphabet[:length] + } + for i := range b { + b[i] = alphabet[int(b[i])%len(alphabet)] + } + return string(b) +} diff --git a/api/pkg/repositories/gorm_message_repository.go b/api/pkg/repositories/gorm_message_repository.go index 607af44e..03237566 100644 --- a/api/pkg/repositories/gorm_message_repository.go +++ b/api/pkg/repositories/gorm_message_repository.go @@ -176,6 +176,37 @@ func (repository *gormMessageRepository) Search(ctx context.Context, userID enti return messages, nil } +// GetBulkMessages fetches the last bulk message summaries for a user +func (repository *gormMessageRepository) GetBulkMessages(ctx context.Context, userID entities.UserID, limit int) ([]*entities.BulkMessage, error) { + ctx, span := repository.tracer.Start(ctx) + defer span.End() + + orders := make([]*entities.BulkMessage, 0) + err := repository.db.WithContext(ctx).Raw(` + SELECT + request_id, + COUNT(*) as total, + COUNT(*) FILTER (WHERE status = 'scheduled') as scheduled_count, + COUNT(*) FILTER (WHERE status = 'pending') as pending_count, + COUNT(*) FILTER (WHERE status = 'failed') as failed_count, + COUNT(*) FILTER (WHERE status = 'expired') as expired_count, + COUNT(*) FILTER (WHERE status = 'sent') as sent_count, + COUNT(*) FILTER (WHERE status = 'delivered') as delivered_count, + MIN(created_at) as created_at + FROM messages + WHERE user_id = ? AND request_id LIKE 'bulk-%' + GROUP BY request_id + ORDER BY MIN(created_at) DESC + LIMIT ? + `, userID, limit).Scan(&orders).Error + if err != nil { + msg := fmt.Sprintf("cannot fetch bulk message orders for user [%s]", userID) + return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)) + } + + return orders, nil +} + // Store a new entities.Message func (repository *gormMessageRepository) Store(ctx context.Context, message *entities.Message) error { ctx, span := repository.tracer.Start(ctx) diff --git a/api/pkg/repositories/message_repository.go b/api/pkg/repositories/message_repository.go index 3ad70015..c8f85fbb 100644 --- a/api/pkg/repositories/message_repository.go +++ b/api/pkg/repositories/message_repository.go @@ -27,6 +27,9 @@ type MessageRepository interface { // Search entities.Message for a user Search(ctx context.Context, userID entities.UserID, owners []string, types []entities.MessageType, statuses []entities.MessageStatus, params IndexParams) ([]*entities.Message, error) + // GetBulkMessages fetches the last bulk message summaries for a user + GetBulkMessages(ctx context.Context, userID entities.UserID, limit int) ([]*entities.BulkMessage, error) + // GetOutstanding fetches an entities.Message which is outstanding GetOutstanding(ctx context.Context, userID entities.UserID, messageID uuid.UUID, phoneNumbers []string) (*entities.Message, error) diff --git a/api/pkg/repositories/mongodb.go b/api/pkg/repositories/mongodb.go index bd6f12be..f76f97bc 100644 --- a/api/pkg/repositories/mongodb.go +++ b/api/pkg/repositories/mongodb.go @@ -107,11 +107,6 @@ func createMongoIndexes(ctx context.Context, db *mongo.Database) error { // Heartbeats indexes heartbeatsCol := db.Collection(collectionHeartbeats) - // TODO: Remove this block after deploying once — old indexes will have been dropped in production. - for _, name := range []string{"owner_1_timestamp_-1", "user_id_1"} { - _ = heartbeatsCol.Indexes().DropOne(ctx, name) - } - _, err := heartbeatsCol.Indexes().CreateMany(ctx, []mongo.IndexModel{ {Keys: bson.D{{"user_id", 1}, {"owner", 1}, {"timestamp", -1}}}, }) diff --git a/api/pkg/requests/bulk_message_request.go b/api/pkg/requests/bulk_message_request.go index 000ff016..b84425fc 100644 --- a/api/pkg/requests/bulk_message_request.go +++ b/api/pkg/requests/bulk_message_request.go @@ -1,24 +1,46 @@ package requests import ( - "fmt" "strings" "time" "github.com/NdoleStudio/httpsms/pkg/entities" "github.com/NdoleStudio/httpsms/pkg/services" - "github.com/google/uuid" "github.com/nyaruka/phonenumbers" ) // BulkMessage represents a single message in a bulk SMS request type BulkMessage struct { request - FromPhoneNumber string `csv:"FromPhoneNumber"` - ToPhoneNumber string `csv:"ToPhoneNumber"` - Content string `csv:"Content"` - SendTime *time.Time `csv:"SendTime(optional)"` - AttachmentURLs string `csv:"AttachmentURLs(optional)" validate:"optional"` // Comma separated list of URLs + FileType string `json:"type"` + FromPhoneNumber string `csv:"FromPhoneNumber"` + ToPhoneNumber string `csv:"ToPhoneNumber"` + Content string `csv:"Content"` + SendTime string `csv:"SendTime(optional)"` + AttachmentURLs string `csv:"AttachmentURLs(optional)" validate:"optional"` // Comma separated list of URLs +} + +// GetSendTime parses the raw SendTime string into a *time.Time +func (input *BulkMessage) GetSendTime() *time.Time { + raw := strings.TrimSpace(input.SendTime) + if raw == "" { + return nil + } + + formats := []string{ + time.RFC3339, + "2006-01-02T15:04:05", + "2006-01-02 15:04:05", + "2006-01-02", + } + + for _, format := range formats { + if t, err := time.Parse(format, raw); err == nil { + utc := t.UTC() + return &utc + } + } + return nil } // Sanitize sets defaults to BulkMessage @@ -38,15 +60,15 @@ func (input *BulkMessage) Sanitize() *BulkMessage { } // ToMessageSendParams converts BulkMessage to services.MessageSendParams -func (input *BulkMessage) ToMessageSendParams(userID entities.UserID, requestID uuid.UUID, source string, index int) services.MessageSendParams { +func (input *BulkMessage) ToMessageSendParams(userID entities.UserID, requestID string, source string, index int) services.MessageSendParams { from, _ := phonenumbers.Parse(input.FromPhoneNumber, phonenumbers.UNKNOWN_REGION) return services.MessageSendParams{ Source: source, Owner: from, - RequestID: input.sanitizeStringPointer(fmt.Sprintf("bulk-%s", requestID.String())), + RequestID: input.sanitizeStringPointer(requestID), UserID: userID, - SendAt: input.SendTime, + SendAt: input.GetSendTime(), RequestReceivedAt: time.Now().UTC(), Contact: input.sanitizeAddress(input.ToPhoneNumber), Content: input.Content, diff --git a/api/pkg/responses/bulk_message_responses.go b/api/pkg/responses/bulk_message_responses.go new file mode 100644 index 00000000..eda242a6 --- /dev/null +++ b/api/pkg/responses/bulk_message_responses.go @@ -0,0 +1,9 @@ +package responses + +import "github.com/NdoleStudio/httpsms/pkg/entities" + +// BulkMessagesResponse is the payload containing []*entities.BulkMessage +type BulkMessagesResponse struct { + response + Data []*entities.BulkMessage `json:"data"` +} diff --git a/api/pkg/services/message_service.go b/api/pkg/services/message_service.go index 929998bd..56766c98 100644 --- a/api/pkg/services/message_service.go +++ b/api/pkg/services/message_service.go @@ -123,6 +123,21 @@ func (service *MessageService) DeleteAllForUser(ctx context.Context, userID enti return nil } +// GetBulkMessages fetches the last bulk message summaries for a user +func (service *MessageService) GetBulkMessages(ctx context.Context, userID entities.UserID) ([]*entities.BulkMessage, error) { + ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger) + defer span.End() + + orders, err := service.repository.GetBulkMessages(ctx, userID, 10) + if err != nil { + msg := fmt.Sprintf("could not fetch bulk messages for user [%s]", userID) + return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)) + } + + ctxLogger.Info(fmt.Sprintf("fetched [%d] bulk messages for user [%s]", len(orders), userID)) + return orders, nil +} + // DeleteMessage deletes a message from the database func (service *MessageService) DeleteMessage(ctx context.Context, source string, message *entities.Message) error { ctx, span := service.tracer.Start(ctx) diff --git a/api/pkg/validators/bulk_message_handler_validator.go b/api/pkg/validators/bulk_message_handler_validator.go index 3fa0c35c..5976c2cc 100644 --- a/api/pkg/validators/bulk_message_handler_validator.go +++ b/api/pkg/validators/bulk_message_handler_validator.go @@ -52,7 +52,7 @@ func NewBulkMessageHandlerValidator( } // ValidateStore validates the requests.BillingUsageHistory request -func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID entities.UserID, header *multipart.FileHeader) ([]*requests.BulkMessage, url.Values) { +func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID entities.UserID, header *multipart.FileHeader) ([]*requests.BulkMessage, string, url.Values) { ctx, span, ctxLogger := v.tracer.StartWithLogger(ctx, v.logger) defer span.End() @@ -61,22 +61,22 @@ func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID result := url.Values{} result.Add("document", "Cannot load your account. Please try again later or contact support.") ctxLogger.Error(v.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot load user [%s]", userID)))) - return nil, result + return nil, "", result } - messages, result := v.parseFile(ctxLogger, user, header) + messages, fileType, result := v.parseFile(ctxLogger, user, header) if len(result) != 0 { - return messages, result + return messages, fileType, result } if len(messages) == 0 { result.Add("document", "The uploaded file doesn't contain any valid records. Make sure you are using the official httpSMS template.") - return messages, result + return messages, fileType, result } if len(messages) > 1000 { result.Add("document", "The uploaded file must contain less than 1000 records.") - return messages, result + return messages, fileType, result } for index, message := range messages { @@ -85,30 +85,32 @@ func (v *BulkMessageHandlerValidator) ValidateStore(ctx context.Context, userID result = v.validateMessages(ctx, messages) if len(result) != 0 { - return messages, result + return messages, fileType, result } result = v.validateOwners(ctx, userID, messages) if len(result) != 0 { - return messages, result + return messages, fileType, result } - return messages, result + return messages, fileType, result } -func (v *BulkMessageHandlerValidator) parseFile(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, url.Values) { +func (v *BulkMessageHandlerValidator) parseFile(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, string, url.Values) { if header.Header.Get("Content-Type") == "text/csv" || strings.HasSuffix(header.Filename, ".csv") { - return v.parseCSV(ctxLogger, user, header) + messages, result := v.parseCSV(ctxLogger, user, header) + return messages, "csv", result } if header.Header.Get("Content-Type") == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" || strings.HasSuffix(header.Filename, ".xlsx") { - return v.parseXlsx(ctxLogger, user, header) + messages, result := v.parseXlsx(ctxLogger, user, header) + return messages, "xls", result } ctxLogger.Error(stacktrace.NewError(fmt.Sprintf("cannot parse file [%s] for user [%s] with content type [%s]", header.Filename, user.ID, header.Header.Get("Content-Type")))) result := url.Values{} result.Add("document", fmt.Sprintf("The file [%s] is not a valid CSV or Excel file.", header.Filename)) - return nil, result + return nil, "", result } func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user *entities.User, header *multipart.FileHeader) ([]*requests.BulkMessage, url.Values) { @@ -138,14 +140,15 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user continue } - var sendAt *time.Time + var sendTimeRaw string if len(row) > 3 && strings.TrimSpace(row[3]) != "" { ctxLogger.Info(fmt.Sprintf("excel time = [%s]", row[3])) - sendAt, err = v.convertExcelTime(user, row[3]) + sendAt, err := v.convertExcelTime(user, row[3]) if err != nil { result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] is not in the correct format e.g [2006-01-02T15:04:05] where 2006 is the year, 01 is January, 02 is the second day of the month and the time is 15:04:05", index+1, row[3])) return nil, result } + sendTimeRaw = sendAt.Format(time.RFC3339) } var attachmentURLs string @@ -157,7 +160,7 @@ func (v *BulkMessageHandlerValidator) parseXlsx(ctxLogger telemetry.Logger, user FromPhoneNumber: strings.TrimSpace(row[0]), ToPhoneNumber: strings.TrimSpace(row[1]), Content: row[2], - SendTime: sendAt, + SendTime: sendTimeRaw, AttachmentURLs: attachmentURLs, }) } @@ -265,8 +268,13 @@ func (v *BulkMessageHandlerValidator) validateMessages(_ context.Context, messag result.Add("document", fmt.Sprintf("Row [%d]: The message content must be less than 1024 characters.", index+2)) } - if message.SendTime != nil && message.SendTime.After(time.Now().Add(420*time.Hour)) { - result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] cannot be more than 20 days (420 hours) in the future.", index+2, message.SendTime.Format(time.RFC3339))) + if strings.TrimSpace(message.SendTime) != "" { + sendTime := message.GetSendTime() + if sendTime == nil { + result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] is not a valid date format. Use RFC3339 (e.g. 2023-11-11T02:10:01Z) or YYYY-MM-DDTHH:MM:SS.", index+2, message.SendTime)) + } else if sendTime.After(time.Now().Add(420 * time.Hour)) { + result.Add("document", fmt.Sprintf("Row [%d]: The SendTime [%s] cannot be more than 20 days (420 hours) in the future.", index+2, sendTime.Format(time.RFC3339))) + } } } return result diff --git a/api/pkg/validators/message_handler_validator.go b/api/pkg/validators/message_handler_validator.go index ee6cf9b2..da6a7a1d 100644 --- a/api/pkg/validators/message_handler_validator.go +++ b/api/pkg/validators/message_handler_validator.go @@ -328,7 +328,7 @@ func (validator MessageHandlerValidator) ValidateMessageSearch(ctx context.Conte "min:0", }, "query": []string{ - "max:20", + "max:50", }, "token": []string{ "required", diff --git a/tests/go.mod b/tests/go.mod index 422d1e0b..1cc657c0 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -1,6 +1,6 @@ module github.com/NdoleStudio/httpsms/tests -go 1.23 +go 1.24.0 require ( github.com/NdoleStudio/httpsms-go v0.0.8 @@ -8,10 +8,19 @@ require ( github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 github.com/wiremock/go-wiremock v1.14.0 + github.com/xuri/excelize/v2 v2.10.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/richardlehane/mscfb v1.0.6 // indirect + github.com/richardlehane/msoleps v1.0.6 // indirect + github.com/tiendc/go-deepcopy v1.7.2 // indirect + github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/text v0.34.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tests/go.sum b/tests/go.sum index 2deb9da0..441bc37e 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -68,6 +68,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8= +github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= +github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= +github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -78,12 +82,20 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/testcontainers/testcontainers-go v0.28.0 h1:1HLm9qm+J5VikzFDYhOd+Zw12NtOl+8drH2E8nTY1r8= github.com/testcontainers/testcontainers-go v0.28.0/go.mod h1:COlDpUXbwW3owtpMkEB1zo9gwb1CoKVKlyrVPejF4AU= +github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= +github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/wiremock/go-wiremock v1.14.0 h1:cVAV98Odg+hySEYKDRUasVo30q7JE/ysrdx5qOmF4f4= github.com/wiremock/go-wiremock v1.14.0/go.mod h1:T5XkKnsKS2asycbUrk2cpxXTEXwa6klHfCWVN8BkhkU= +github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= +github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0= +github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg= @@ -94,14 +106,24 @@ go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPi go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= diff --git a/tests/helpers_test.go b/tests/helpers_test.go index 6b4a0782..49a7a654 100644 --- a/tests/helpers_test.go +++ b/tests/helpers_test.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "math/big" + "mime/multipart" "net/http" "strings" "testing" @@ -315,3 +316,99 @@ func waitForFCMPush(t *testing.T, messageID string, timeout time.Duration) []wmJ t.Fatalf("FCM push for message %s not found within %v", messageID, timeout) return nil } + +type BulkMessageEntry struct { + RequestID string `json:"request_id"` + Total int `json:"total"` + ScheduledCount int `json:"scheduled_count"` + PendingCount int `json:"pending_count"` + FailedCount int `json:"failed_count"` + ExpiredCount int `json:"expired_count"` + SentCount int `json:"sent_count"` + DeliveredCount int `json:"delivered_count"` + CreatedAt string `json:"created_at"` +} + +func uploadBulkFile(ctx context.Context, t *testing.T, filename string, fileBytes []byte) (int, []byte) { + t.Helper() + + var buf bytes.Buffer + writer := multipart.NewWriter(&buf) + + part, err := writer.CreateFormFile("document", filename) + require.NoError(t, err) + + _, err = part.Write(fileBytes) + require.NoError(t, err) + require.NoError(t, writer.Close()) + + url := apiBaseURL + "/v1/bulk-messages" + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &buf) + require.NoError(t, err) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req.Header.Set("x-api-key", userAPIKey) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + return resp.StatusCode, body +} + +func fetchBulkMessages(ctx context.Context, t *testing.T) []BulkMessageEntry { + t.Helper() + + url := apiBaseURL + "/v1/bulk-messages" + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + require.NoError(t, err) + req.Header.Set("x-api-key", userAPIKey) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode, "fetch bulk messages failed: %s", string(body)) + + var result struct { + Data []BulkMessageEntry `json:"data"` + } + require.NoError(t, json.Unmarshal(body, &result)) + return result.Data +} + +func searchMessages(ctx context.Context, t *testing.T, contact string, owner string) []httpsms.Message { + t.Helper() + + url := fmt.Sprintf("%s/v1/messages?contact=%s&owner=%s&limit=20&skip=0", apiBaseURL, contact, owner) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + require.NoError(t, err) + req.Header.Set("x-api-key", userAPIKey) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode, "search messages failed: %s", string(body)) + + var result struct { + Data []httpsms.Message `json:"data"` + } + require.NoError(t, json.Unmarshal(body, &result)) + return result.Data +} + +func findBulkEntry(entries []BulkMessageEntry, requestID string) *BulkMessageEntry { + for i := range entries { + if entries[i].RequestID == requestID { + return &entries[i] + } + } + return nil +} diff --git a/tests/integration_test.go b/tests/integration_test.go index 12776aac..29d4ced8 100644 --- a/tests/integration_test.go +++ b/tests/integration_test.go @@ -14,6 +14,7 @@ import ( httpsms "github.com/NdoleStudio/httpsms-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/xuri/excelize/v2" ) func TestSendSMS_Encrypted(t *testing.T) { @@ -402,3 +403,158 @@ func TestHeartbeat_StoreAndIndex(t *testing.T) { assert.True(t, hb.Charging) assert.False(t, hb.Timestamp.IsZero(), "timestamp should not be zero") } + +func TestBulkSMS_CSV(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + + // Build CSV content with 1 message + contact := randomPhoneNumber() + csvContent := fmt.Sprintf("FromPhoneNumber,ToPhoneNumber,Content,SendTime(optional)\n%s,%s,CSV bulk test message,\n", + phone.PhoneNumber, contact) + + // Upload CSV + statusCode, respBody := uploadBulkFile(ctx, t, "test.csv", []byte(csvContent)) + require.Equal(t, http.StatusAccepted, statusCode, "upload failed: %s", string(respBody)) + t.Logf("upload response: %s", string(respBody)) + + // Parse the response to verify message count + var uploadResp struct { + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(respBody, &uploadResp)) + assert.Contains(t, uploadResp.Message, "1 out of 1") + + // Wait a moment for messages to be persisted + time.Sleep(2 * time.Second) + + // Search for the bulk message by owner to get message IDs + messages := searchMessages(ctx, t, contact, phone.PhoneNumber) + require.GreaterOrEqual(t, len(messages), 1, "expected at least 1 message for phone %s", phone.PhoneNumber) + + // Find the message with bulk- request_id prefix + var bulkMsg *httpsms.Message + for i := range messages { + if messages[i].RequestID != nil && strings.HasPrefix(*messages[i].RequestID, "bulk-") { + bulkMsg = &messages[i] + break + } + } + require.NotNil(t, bulkMsg, "no message with bulk- request_id found") + messageID := bulkMsg.ID.String() + requestID := *bulkMsg.RequestID + t.Logf("found bulk message: id=%s, request_id=%s", messageID, requestID) + + // Wait for FCM push + waitForFCMPush(t, messageID, 30*time.Second) + + // Fire SENT event + fireEvent(ctx, t, phone.PhoneAPIKey, messageID, "SENT") + + // Poll until message reaches "sent" status + msg := pollMessageStatus(ctx, t, messageID, "sent", 15*time.Second) + assert.Equal(t, "sent", msg.Status) + + // Verify bulk-messages history endpoint + entries := fetchBulkMessages(ctx, t) + entry := findBulkEntry(entries, requestID) + require.NotNil(t, entry, "bulk entry with request_id %s not found in history", requestID) + + assert.Equal(t, 1, entry.Total) + assert.Equal(t, 1, entry.SentCount) + assert.Equal(t, 0, entry.PendingCount) + assert.Equal(t, 0, entry.FailedCount) + assert.Equal(t, 0, entry.ExpiredCount) + assert.Equal(t, 0, entry.DeliveredCount) + assert.Equal(t, 0, entry.ScheduledCount) +} + +func TestBulkSMS_Excel(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + + contact1 := randomPhoneNumber() + contact2 := randomPhoneNumber() + + // Build Excel file with 2 messages + f := excelize.NewFile() + sheet := f.GetSheetName(0) + f.SetCellValue(sheet, "A1", "FromPhoneNumber") + f.SetCellValue(sheet, "B1", "ToPhoneNumber") + f.SetCellValue(sheet, "C1", "Content") + f.SetCellValue(sheet, "D1", "SendTime(optional)") + + f.SetCellValue(sheet, "A2", phone.PhoneNumber) + f.SetCellValue(sheet, "B2", contact1) + f.SetCellValue(sheet, "C2", "Excel bulk test message 1") + f.SetCellValue(sheet, "D2", "") + + f.SetCellValue(sheet, "A3", phone.PhoneNumber) + f.SetCellValue(sheet, "B3", contact2) + f.SetCellValue(sheet, "C3", "Excel bulk test message 2") + f.SetCellValue(sheet, "D3", "") + + var excelBuf bytes.Buffer + require.NoError(t, f.Write(&excelBuf)) + + // Upload Excel + statusCode, respBody := uploadBulkFile(ctx, t, "test.xlsx", excelBuf.Bytes()) + require.Equal(t, http.StatusAccepted, statusCode, "upload failed: %s", string(respBody)) + t.Logf("upload response: %s", string(respBody)) + + var uploadResp struct { + Message string `json:"message"` + } + require.NoError(t, json.Unmarshal(respBody, &uploadResp)) + assert.Contains(t, uploadResp.Message, "2 out of 2") + + // Wait for messages to be persisted + time.Sleep(2 * time.Second) + + // Search for bulk messages by owner and each contact + messages1 := searchMessages(ctx, t, contact1, phone.PhoneNumber) + messages2 := searchMessages(ctx, t, contact2, phone.PhoneNumber) + messages := append(messages1, messages2...) + require.GreaterOrEqual(t, len(messages), 2, "expected at least 2 messages for phone %s", phone.PhoneNumber) + + // Find messages with bulk- request_id prefix + var bulkMessages []httpsms.Message + var requestID string + for i := range messages { + if messages[i].RequestID != nil && strings.HasPrefix(*messages[i].RequestID, "bulk-") { + bulkMessages = append(bulkMessages, messages[i]) + requestID = *messages[i].RequestID + } + } + require.Len(t, bulkMessages, 2, "expected 2 messages with bulk- request_id") + require.NotEmpty(t, requestID) + t.Logf("found %d bulk messages with request_id=%s", len(bulkMessages), requestID) + + // Wait for FCM pushes for both messages + msgID1 := bulkMessages[0].ID.String() + msgID2 := bulkMessages[1].ID.String() + waitForFCMPush(t, msgID1, 30*time.Second) + waitForFCMPush(t, msgID2, 30*time.Second) + + // Fire SENT then DELIVERED on message 1, leave message 2 pending + fireEvent(ctx, t, phone.PhoneAPIKey, msgID1, "SENT") + time.Sleep(200 * time.Millisecond) + fireEvent(ctx, t, phone.PhoneAPIKey, msgID1, "DELIVERED") + + // Poll until message 1 reaches "delivered" + msg1 := pollMessageStatus(ctx, t, msgID1, "delivered", 15*time.Second) + assert.Equal(t, "delivered", msg1.Status) + + // Verify bulk-messages history endpoint + entries := fetchBulkMessages(ctx, t) + entry := findBulkEntry(entries, requestID) + require.NotNil(t, entry, "bulk entry with request_id %s not found in history", requestID) + + assert.Equal(t, 2, entry.Total) + assert.Equal(t, 1, entry.DeliveredCount) + assert.Equal(t, 0, entry.PendingCount) + assert.Equal(t, 0, entry.SentCount) + assert.Equal(t, 0, entry.FailedCount) + assert.Equal(t, 0, entry.ExpiredCount) + assert.Equal(t, 1, entry.ScheduledCount) +} diff --git a/web/models/api.ts b/web/models/api.ts index 033825ca..c4d7b8dc 100644 --- a/web/models/api.ts +++ b/web/models/api.ts @@ -50,6 +50,25 @@ export interface EntitiesBillingUsage { user_id: string } +export interface EntitiesBulkMessage { + /** @example "2022-06-05T14:26:02.302718+03:00" */ + created_at: string + /** @example 25 */ + delivered_count: number + /** @example 5 */ + failed_count: number + /** @example 30 */ + pending_count: number + /** @example "bulk-32343a19-da5e-4b1b-a767-3298a73703cb" */ + request_id: string + /** @example 50 */ + scheduled_count: number + /** @example 40 */ + sent_count: number + /** @example 150 */ + total: number +} + export interface EntitiesDiscord { /** @example "2022-06-05T14:26:02.302718+03:00" */ created_at: string @@ -582,6 +601,14 @@ export interface ResponsesBillingUsagesResponse { status: string } +export interface ResponsesBulkMessagesResponse { + data: EntitiesBulkMessage[] + /** @example "Request handled successfully" */ + message: string + /** @example "success" */ + status: string +} + export interface ResponsesDiscordResponse { data: EntitiesDiscord /** @example "Request handled successfully" */ diff --git a/web/pages/bulk-messages/index.vue b/web/pages/bulk-messages/index.vue index 3182530a..b2935bcd 100644 --- a/web/pages/bulk-messages/index.vue +++ b/web/pages/bulk-messages/index.vue @@ -96,6 +96,62 @@ + + + Bulk Message History + + Your 10 most recent bulk SMS uploads are shown below, including a + delivery status breakdown for each batch. Click on a row to see + individual messages on the search page. + + + + + + + ID + Created At + Total + Pending + Scheduled + Sent + Delivered + Failed + Expired + + + + + + {{ cleanName(order.request_id) }} + + + {{ order.created_at | timestamp }} + + {{ order.total }} + {{ order.pending_count }} + {{ order.scheduled_count }} + {{ order.sent_count }} + {{ order.delivered_count }} + {{ order.failed_count }} + {{ order.expired_count }} + + + + + + @@ -145,9 +201,11 @@ export default Vue.extend({ mdiSquareEditOutline, formFile: null, loading: true, + loadingHistory: true, errorTitle: '', errorMessages: new ErrorMessages(), dialog: false, + bulkOrders: [] as any[], } }, head() { @@ -159,8 +217,32 @@ export default Vue.extend({ async mounted() { await this.$store.dispatch('loadUser') this.loading = false + this.fetchBulkOrders() }, methods: { + cleanName(requestId: string): string { + if (requestId.startsWith('bulk-csv-')) { + return requestId.replace(/^bulk-csv-/, '') + '.csv' + } + if (requestId.startsWith('bulk-xls-')) { + return requestId.replace(/^bulk-xls-/, '') + '.xlsx' + } + return requestId.replace(/^bulk-/, '') + }, + fetchBulkOrders() { + this.loadingHistory = true + this.$store + .dispatch('fetchBulkMessageOrders') + .then((orders: any[]) => { + this.bulkOrders = orders + }) + .catch(() => { + // silently fail - the table will show "no data" + }) + .finally(() => { + this.loadingHistory = false + }) + }, sendBulkMessages() { this.loading = true this.errorMessages = new ErrorMessages() @@ -186,3 +268,12 @@ export default Vue.extend({ }, }) + + diff --git a/web/pages/search-messages/index.vue b/web/pages/search-messages/index.vue index 9fd1f667..67062625 100644 --- a/web/pages/search-messages/index.vue +++ b/web/pages/search-messages/index.vue @@ -325,6 +325,7 @@ export default Vue.extend({ mdiCallMade, mdiProgressCheck, loading: true, + initialLoadComplete: false, errorTitle: '', showDeleteDialog: false, selectedMessages: [] as EntitiesMessage[], @@ -388,6 +389,9 @@ export default Vue.extend({ watch: { options: { handler() { + if (!this.initialLoadComplete) { + return + } this.fetchMessages() }, deep: true, @@ -396,7 +400,20 @@ export default Vue.extend({ async mounted() { await this.$store.dispatch('loadUser') await this.$store.dispatch('loadPhones') + + // Auto-fill search query from URL params + const queryParam = this.$route.query.query + if (queryParam && typeof queryParam === 'string') { + this.formQuery = queryParam + } + this.loading = false + this.initialLoadComplete = true + + // Auto-search if query param was provided + if (this.formQuery) { + this.fetchMessages(true) + } }, methods: { diff --git a/web/store/index.ts b/web/store/index.ts index fc53c395..9077ccf6 100644 --- a/web/store/index.ts +++ b/web/store/index.ts @@ -395,6 +395,23 @@ export const actions = { } }, + fetchBulkMessageOrders(context: ActionContext) { + return new Promise((resolve, reject) => { + axios + .get<{ data: any[] }>(`/v1/bulk-messages`) + .then((response) => { + resolve(response.data.data ?? []) + }) + .catch(async (error: AxiosError) => { + await context.dispatch('addNotification', { + message: 'Error while fetching bulk messages history', + type: 'error', + }) + reject(error) + }) + }) + }, + sendBulkMessages(context: ActionContext, document: File) { return new Promise((resolve, reject) => { const formData = new FormData()
+ Your 10 most recent bulk SMS uploads are shown below, including a + delivery status breakdown for each batch. Click on a row to see + individual messages on the search page. +