diff --git a/api/docs/docs.go b/api/docs/docs.go index 26545bdb..9b3adba6 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -1,5 +1,4 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Package docs Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -1429,6 +1428,72 @@ const docTemplate = `{ } }, "/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": [ { @@ -2114,6 +2179,280 @@ const docTemplate = `{ } } }, + "/send-schedules": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Lists the send schedules owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "List send schedules", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendSchedulesResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Creates a send schedule for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Create send schedule", + "parameters": [ + { + "description": "Payload of new send schedule.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.SendScheduleStore" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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/{scheduleID}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Loads a single send schedule owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Show send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates a send schedule owned by the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "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.SendScheduleStore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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": "Deletes a send schedule owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Delete send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "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" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + } + }, "/users/me": { "get": { "security": [ @@ -3118,7 +3457,11 @@ const docTemplate = `{ }, "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", - "type": "string", + "allOf": [ + { + "$ref": "#/definitions/entities.SIM" + } + ], "example": "DEFAULT" }, "status": { @@ -3215,6 +3558,7 @@ const docTemplate = `{ "message_expiration_seconds", "messages_per_minute", "phone_number", + "schedule_id", "sim", "updated_at", "user_id" @@ -3253,9 +3597,12 @@ const docTemplate = `{ "type": "string", "example": "+18005550199" }, + "schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, "sim": { - "description": "SIM card that received the message", - "type": "string" + "$ref": "#/definitions/entities.SIM" }, "updated_at": { "type": "string", @@ -3331,6 +3678,117 @@ const docTemplate = `{ } } }, + "entities.SIM": { + "type": "string", + "enum": [ + "SIM1", + "SIM2" + ], + "x-enum-varnames": [ + "SIM1", + "SIM2" + ] + }, + "entities.SendSchedule": { + "type": "object", + "required": [ + "created_at", + "id", + "is_active", + "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" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "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.SendScheduleWindow" + } + } + } + }, + "entities.SendScheduleWindow": { + "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.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": [ @@ -3393,7 +3851,11 @@ const docTemplate = `{ "example": "8f9c71b8-b84e-4417-8408-a62274f65a08" }, "subscription_name": { - "type": "string", + "allOf": [ + { + "$ref": "#/definitions/entities.SubscriptionName" + } + ], "example": "free" }, "subscription_renews_at": { @@ -3644,7 +4106,11 @@ const docTemplate = `{ }, "sim": { "description": "SIM card that received the message", - "type": "string", + "allOf": [ + { + "$ref": "#/definitions/entities.SIM" + } + ], "example": "SIM1" }, "timestamp": { @@ -3751,6 +4217,7 @@ const docTemplate = `{ "messages_per_minute", "missed_call_auto_reply", "phone_number", + "schedule_id", "sim" ], "properties": { @@ -3780,6 +4247,10 @@ const docTemplate = `{ "type": "string", "example": "+18005550199" }, + "schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, "sim": { "description": "SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot", "type": "string", @@ -3787,6 +4258,51 @@ const docTemplate = `{ } } }, + "requests.SendScheduleStore": { + "type": "object", + "required": [ + "is_active", + "name", + "timezone", + "windows" + ], + "properties": { + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "windows": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.SendScheduleWindow" + } + } + } + }, + "requests.SendScheduleWindow": { + "type": "object", + "required": [ + "day_of_week", + "end_minute", + "start_minute" + ], + "properties": { + "day_of_week": { + "type": "integer" + }, + "end_minute": { + "type": "integer" + }, + "start_minute": { + "type": "integer" + } + } + }, "requests.UserNotificationUpdate": { "type": "object", "required": [ @@ -4327,6 +4843,51 @@ const docTemplate = `{ } } }, + "responses.SendScheduleResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.SendSchedule" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } + }, + "responses.SendSchedulesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.SendSchedule" + } + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } + }, "responses.Unauthorized": { "type": "object", "required": [ @@ -4612,6 +5173,8 @@ var SwaggerInfo = &swag.Spec{ Description: "Use your Android phone to send and receive SMS messages via a simple programmable API with end-to-end encryption.", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/api/docs/swagger.json b/api/docs/swagger.json index db16ea4b..430e52b1 100644 --- a/api/docs/swagger.json +++ b/api/docs/swagger.json @@ -1,4166 +1,5161 @@ { - "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}": { - "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" - } - } - } - } - }, - "/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" - } - } - ], - "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" - } - } - } - } - }, - "/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": [ - "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": { - "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", - "type": "string", - "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.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" - }, - "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": { - "description": "SIM card that received the message", - "type": "string" + "name": "support@httpsms.com", + "email": "support@httpsms.com" }, - "updated_at": { - "type": "string", - "example": "2022-06-05T14:26:10.303278+03:00" + "license": { + "name": "AGPL-3.0", + "url": "https://raw.githubusercontent.com/NdoleStudio/http-sms-manager/main/LICENSE" }, - "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" - }, - "user_email": { - "type": "string", - "example": "user@gmail.com" - }, - "user_id": { - "type": "string", - "example": "WB7DRDWrJZRGbYrv2CKGkqbzvqdC" - } - } - }, - "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": { - "type": "string", - "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" - } - } + "version": "1.0" }, - "requests.DiscordStore": { - "type": "object", - "required": ["incoming_channel_id", "name", "server_id"], - "properties": { - "incoming_channel_id": { - "type": "string" + "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" + } + } + } + } }, - "name": { - "type": "string" + "/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" + } + } + } + } }, - "server_id": { - "type": "string" - } - } - }, - "requests.DiscordUpdate": { - "type": "object", - "required": ["incoming_channel_id", "name", "server_id"], - "properties": { - "incoming_channel_id": { - "type": "string" + "/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" + } + } + } + } }, - "name": { - "type": "string" + "/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" + } + } + } + } }, - "server_id": { - "type": "string" - } - } - }, - "requests.HeartbeatStore": { - "type": "object", - "required": ["charging", "phone_numbers"], - "properties": { - "charging": { - "type": "boolean" + "/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" + } + } + } + } }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "requests.MessageBulkSend": { - "type": "object", - "required": ["content", "encrypted", "from", "to"], - "properties": { - "content": { - "type": "string", - "example": "This is a sample text message" + "/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" + } + } + } + } }, - "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 + "/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" + } + } + } + } }, - "from": { - "type": "string", - "example": "+18005550199" + "/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" + } + } + } + } }, - "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" + "/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" + } + } + } + } }, - "to": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] - } - } - }, - "requests.MessageCallMissed": { - "type": "object", - "required": ["from", "sim", "timestamp", "to"], - "properties": { - "from": { - "type": "string", - "example": "+18005550199" + "/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" + } + } + } + } }, - "sim": { - "type": "string", - "example": "SIM1" + "/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" + } + } + } + } }, - "timestamp": { - "type": "string", - "example": "2022-06-05T14:26:09.527976+03:00" + "/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" + } + } + } + } }, - "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" + "/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" + } + } + } + } }, - "reason": { - "description": "Reason is the exact error message in case the event is an error", - "type": "string" + "/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" + } + } + } + } }, - "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": { - "content": { - "type": "string", - "example": "This is a sample text message received on a phone" + "/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" + } + } + } + } }, - "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/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" + } + } + } + } }, - "from": { - "type": "string", - "example": "+18005550199" + "/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" + } + } + } + } }, - "sim": { - "description": "SIM card that received the message", - "type": "string", - "example": "SIM1" + "/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" + } + } + } + } }, - "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/{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" + } + } + } + } }, - "to": { - "type": "string", - "example": "+18005550100" - } - } - }, - "requests.MessageSend": { - "type": "object", - "required": ["content", "from", "to"], - "properties": { - "content": { - "type": "string", - "example": "This is a sample text message" + "/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" + } + } + } + } }, - "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 + "/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" + } + } + } + } }, - "from": { - "type": "string", - "example": "+18005550199" + "/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" + } + } + } + } }, - "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" + "/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" + } + } + } + } }, - "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" + "/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" + } + } + } + } }, - "to": { - "type": "string", - "example": "+18005550100" - } - } - }, - "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": "Lists the send schedules owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "List send schedules", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendSchedulesResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Creates a send schedule for the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Create send schedule", + "parameters": [ + { + "description": "Payload of new send schedule.", + "name": "payload", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/requests.SendScheduleStore" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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.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}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Loads a single send schedule owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Show send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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" + } + } + } + }, + "put": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Updates a send schedule owned by the authenticated user.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "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.SendScheduleStore" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.SendScheduleResponse" + } + }, + "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": "Deletes a send schedule owned by the authenticated user.", + "produces": [ + "application/json" + ], + "tags": [ + "Send Schedules" + ], + "summary": "Delete send schedule", + "parameters": [ + { + "type": "string", + "description": "Schedule ID", + "name": "scheduleID", + "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" + } + }, + "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" + } + } + } + } }, - "messages_per_minute": { - "type": "integer", - "example": 1 + "/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" + } + } + } + } }, - "missed_call_auto_reply": { - "type": "string", - "example": "e.g. This phone cannot receive calls. Please send an SMS instead." + "/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" + } + } + ], + "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" + } + } + } + } }, - "phone_number": { - "type": "string", - "example": "+18005550199" + "/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" + } + } + } + } }, - "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}/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" + } + } + } + } }, - "message_status_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" + } + } + } + } }, - "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" + "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" + } + } }, - "country": { - "type": "string", - "example": "US" + "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" + } + } }, - "name": { - "type": "string", - "example": "Acme Corp" + "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" + } + } }, - "notes": { - "type": "string", - "example": "Thank you for your business!" + "entities.Message": { + "type": "object", + "required": [ + "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": { + "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" + } + } }, - "state": { - "type": "string", - "example": "CA" + "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" + } + } }, - "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.Phone": { + "type": "object", + "required": [ + "created_at", + "id", + "max_send_attempts", + "message_expiration_seconds", + "messages_per_minute", + "phone_number", + "schedule_id", + "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" + }, + "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" + }, + "schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "sim": { + "$ref": "#/definitions/entities.SIM" + }, + "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.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" + } + } }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] + "entities.SIM": { + "type": "string", + "enum": [ + "SIM1", + "SIM2" + ], + "x-enum-varnames": [ + "SIM1", + "SIM2" + ] + }, + "entities.SendSchedule": { + "type": "object", + "required": [ + "created_at", + "id", + "is_active", + "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" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "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.SendScheduleWindow" + } + } + } }, - "signing_key": { - "type": "string" + "entities.SendScheduleWindow": { + "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 + } + } }, - "url": { - "type": "string" - } - } - }, - "requests.WebhookUpdate": { - "type": "object", - "required": ["events", "phone_numbers", "signing_key", "url"], - "properties": { - "events": { - "type": "array", - "items": { - "type": "string" - } + "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" + } + } }, - "phone_numbers": { - "type": "array", - "items": { - "type": "string" - }, - "example": ["+18005550100", "+18005550100"] + "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" + } + } }, - "signing_key": { - "type": "string" + "requests.DiscordStore": { + "type": "object", + "required": [ + "incoming_channel_id", + "name", + "server_id" + ], + "properties": { + "incoming_channel_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "server_id": { + "type": "string" + } + } }, - "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" + "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": "The request isn't properly formed" + "requests.HeartbeatStore": { + "type": "object", + "required": [ + "charging", + "phone_numbers" + ], + "properties": { + "charging": { + "type": "boolean" + }, + "phone_numbers": { + "type": "array", + "items": { + "type": "string" + } + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.BillingUsageResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.BillingUsage" + "requests.MessageBulkSend": { + "type": "object", + "required": [ + "content", + "encrypted", + "from", + "to" + ], + "properties": { + "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" + ] + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.BillingUsagesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.BillingUsage" - } + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageReceive": { + "type": "object", + "required": [ + "content", + "encrypted", + "from", + "sim", + "timestamp", + "to" + ], + "properties": { + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.DiscordResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Discord" + "requests.MessageSend": { + "type": "object", + "required": [ + "content", + "from", + "to" + ], + "properties": { + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "requests.MessageThreadUpdate": { + "type": "object", + "required": [ + "is_archived" + ], + "properties": { + "is_archived": { + "type": "boolean", + "example": true + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.DiscordsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Discord" - } + "requests.PhoneAPIKeyStoreRequest": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "example": "My Phone API Key" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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": "success" - } - } - }, - "responses.HeartbeatResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Heartbeat" + "requests.PhoneUpsert": { + "type": "object", + "required": [ + "fcm_token", + "max_send_attempts", + "message_expiration_seconds", + "messages_per_minute", + "missed_call_auto_reply", + "phone_number", + "schedule_id", + "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 + }, + "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" + }, + "schedule_id": { + "type": "string", + "example": "32343a19-da5e-4b1b-a767-3298a73703cb" + }, + "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.SendScheduleStore": { + "type": "object", + "required": [ + "is_active", + "name", + "timezone", + "windows" + ], + "properties": { + "is_active": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "timezone": { + "type": "string" + }, + "windows": { + "type": "array", + "items": { + "$ref": "#/definitions/requests.SendScheduleWindow" + } + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.HeartbeatsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Heartbeat" - } + "requests.SendScheduleWindow": { + "type": "object", + "required": [ + "day_of_week", + "end_minute", + "start_minute" + ], + "properties": { + "day_of_week": { + "type": "integer" + }, + "end_minute": { + "type": "integer" + }, + "start_minute": { + "type": "integer" + } + } }, - "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.InternalServerError": { - "type": "object", - "required": ["message", "status"], - "properties": { - "message": { - "type": "string", - "example": "We ran into an internal error while handling the request." + "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" + } + } }, - "status": { - "type": "string", - "example": "error" - } - } - }, - "responses.MessageResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Message" + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessageThreadsResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.MessageThread" - } + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.MessagesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Message" - } + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "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.PhoneAPIKeyResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.PhoneAPIKey" + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhoneAPIKeysResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.PhoneAPIKey" - } + "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" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhoneResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "$ref": "#/definitions/entities.Phone" + "responses.NoContent": { + "type": "object", + "required": [ + "message", + "status" + ], + "properties": { + "message": { + "type": "string", + "example": "action performed successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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" + } + } }, - "status": { - "type": "string", - "example": "success" - } - } - }, - "responses.PhonesResponse": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/entities.Phone" - } + "responses.OkString": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "string" + }, + "message": { + "type": "string", + "example": "Request handled successfully" + }, + "status": { + "type": "string", + "example": "success" + } + } }, - "message": { - "type": "string", - "example": "Request handled successfully" + "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": "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.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": "You are not authorized to carry out this 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.UnprocessableEntity": { - "type": "object", - "required": ["data", "message", "status"], - "properties": { - "data": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" + "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": "validation errors while handling request" + "responses.SendScheduleResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "$ref": "#/definitions/entities.SendSchedule" + }, + "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.SendSchedulesResponse": { + "type": "object", + "required": [ + "data", + "message", + "status" + ], + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/entities.SendSchedule" + } + }, + "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" } - } -} +} \ No newline at end of file diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index e8b171eb..e4bc41b1 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -30,15 +30,15 @@ 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.Discord: properties: @@ -64,13 +64,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,12 +93,12 @@ 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: @@ -160,8 +160,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 @@ -169,13 +168,14 @@ definitions: example: "2022-06-05T14:26:09.527976+03:00" type: string sim: + allOf: + - $ref: '#/definitions/entities.SIM' description: |- SIM is the SIM card to use to send the message * SMS1: use the SIM card in slot 1 * SMS2: use the SIM card in slot 2 * DEFAULT: used the default communication SIM card example: DEFAULT - type: string status: example: pending type: string @@ -189,21 +189,21 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: - - 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 + - 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.MessageThread: properties: @@ -244,18 +244,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: @@ -269,14 +269,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 messages_per_minute: @@ -288,9 +286,11 @@ definitions: phone_number: example: "+18005550199" type: string - sim: - description: SIM card that received the message + schedule_id: + example: 32343a19-da5e-4b1b-a767-3298a73703cb type: string + sim: + $ref: '#/definitions/entities.SIM' updated_at: example: "2022-06-05T14:26:10.303278+03:00" type: string @@ -298,15 +298,16 @@ 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 + - schedule_id + - sim + - updated_at + - user_id type: object entities.PhoneAPIKey: properties: @@ -324,15 +325,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 @@ -346,16 +347,103 @@ 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 + type: string + x-enum-varnames: + - SIM1 + - SIM2 + entities.SendSchedule: + properties: + created_at: + example: "2022-06-05T14:26:02.302718+03:00" + type: string + id: + example: 32343a19-da5e-4b1b-a767-3298a73703cb + type: string + is_active: + example: true + type: boolean + name: + example: Business Hours + type: string + timezone: + example: Europe/Tallinn + type: string + updated_at: + example: "2022-06-05T14:26:10.303278+03:00" + type: string + user_id: + example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC + type: string + windows: + items: + $ref: '#/definitions/entities.SendScheduleWindow' + type: array + required: + - created_at + - id + - is_active + - name + - timezone + - updated_at + - user_id + - windows + type: object + entities.SendScheduleWindow: + properties: + day_of_week: + example: 1 + type: integer + end_minute: + example: 1020 + type: integer + start_minute: + example: 540 + type: integer + required: + - day_of_week + - end_minute + - start_minute type: object + entities.SubscriptionName: + enum: + - 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 entities.User: properties: active_phone_id: @@ -392,8 +480,9 @@ definitions: example: 8f9c71b8-b84e-4417-8408-a62274f65a08 type: string subscription_name: + allOf: + - $ref: '#/definitions/entities.SubscriptionName' example: free - type: string subscription_renews_at: example: "2022-06-05T14:26:02.302718+03:00" type: string @@ -407,18 +496,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: @@ -427,7 +516,7 @@ definitions: type: string events: example: - - message.phone.received + - message.phone.received items: type: string type: array @@ -436,8 +525,8 @@ definitions: type: string phone_numbers: example: - - "+18005550199" - - "+18005550100" + - "+18005550199" + - "+18005550100" items: type: string type: array @@ -454,14 +543,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: @@ -472,9 +561,9 @@ definitions: server_id: type: string required: - - incoming_channel_id - - name - - server_id + - incoming_channel_id + - name + - server_id type: object requests.DiscordUpdate: properties: @@ -485,9 +574,9 @@ definitions: server_id: type: string required: - - incoming_channel_id - - name - - server_id + - incoming_channel_id + - name + - server_id type: object requests.HeartbeatStore: properties: @@ -498,8 +587,8 @@ definitions: type: string type: array required: - - charging - - phone_numbers + - charging + - phone_numbers type: object requests.MessageBulkSend: properties: @@ -507,8 +596,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 @@ -516,23 +604,22 @@ 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 - - encrypted - - from - - to + - content + - encrypted + - from + - to type: object requests.MessageCallMissed: properties: @@ -549,10 +636,10 @@ definitions: example: "+18005550100" type: string required: - - from - - sim - - timestamp - - to + - from + - sim + - timestamp + - to type: object requests.MessageEvent: properties: @@ -568,15 +655,14 @@ 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: @@ -584,8 +670,7 @@ definitions: 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 @@ -593,12 +678,12 @@ definitions: example: "+18005550199" type: string sim: + allOf: + - $ref: '#/definitions/entities.SIM' description: SIM card that received the message example: SIM1 - 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 @@ -606,12 +691,12 @@ definitions: example: "+18005550100" type: string required: - - content - - encrypted - - from - - sim - - timestamp - - to + - content + - encrypted + - from + - sim + - timestamp + - to type: object requests.MessageSend: properties: @@ -619,8 +704,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 @@ -629,14 +713,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. @@ -646,9 +728,9 @@ definitions: example: "+18005550100" type: string required: - - content - - from - - to + - content + - from + - to type: object requests.MessageThreadUpdate: properties: @@ -656,7 +738,7 @@ definitions: example: true type: boolean required: - - is_archived + - is_archived type: object requests.PhoneAPIKeyStoreRequest: properties: @@ -664,7 +746,7 @@ definitions: example: My Phone API Key type: string required: - - name + - name type: object requests.PhoneFCMToken: properties: @@ -672,18 +754,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: @@ -691,14 +772,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 @@ -711,20 +790,54 @@ definitions: phone_number: example: "+18005550199" type: string + schedule_id: + example: 32343a19-da5e-4b1b-a767-3298a73703cb + 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 + - schedule_id + - sim + type: object + requests.SendScheduleStore: + properties: + is_active: + type: boolean + name: + type: string + timezone: + type: string + windows: + items: + $ref: '#/definitions/requests.SendScheduleWindow' + type: array + required: + - is_active + - name + - timezone + - windows + type: object + requests.SendScheduleWindow: + properties: + day_of_week: + type: integer + end_minute: + type: integer + start_minute: + type: integer + required: + - day_of_week + - end_minute + - start_minute type: object requests.UserNotificationUpdate: properties: @@ -741,10 +854,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: @@ -770,13 +883,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: @@ -787,8 +900,8 @@ definitions: example: Europe/Helsinki type: string required: - - active_phone_id - - timezone + - active_phone_id + - timezone type: object requests.WebhookStore: properties: @@ -798,8 +911,8 @@ definitions: type: array phone_numbers: example: - - "+18005550100" - - "+18005550100" + - "+18005550100" + - "+18005550100" items: type: string type: array @@ -808,10 +921,10 @@ definitions: url: type: string required: - - events - - phone_numbers - - signing_key - - url + - events + - phone_numbers + - signing_key + - url type: object requests.WebhookUpdate: properties: @@ -821,8 +934,8 @@ definitions: type: array phone_numbers: example: - - "+18005550100" - - "+18005550100" + - "+18005550100" + - "+18005550100" items: type: string type: array @@ -831,10 +944,10 @@ definitions: url: type: string required: - - events - - phone_numbers - - signing_key - - url + - events + - phone_numbers + - signing_key + - url type: object responses.BadRequest: properties: @@ -848,14 +961,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 @@ -863,15 +976,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 @@ -880,14 +993,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.DiscordResponse: properties: data: - $ref: "#/definitions/entities.Discord" + $ref: '#/definitions/entities.Discord' message: example: Request handled successfully type: string @@ -895,15 +1008,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 @@ -912,14 +1025,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 @@ -927,15 +1040,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 @@ -944,9 +1057,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.InternalServerError: properties: @@ -957,13 +1070,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 @@ -971,15 +1084,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 @@ -988,15 +1101,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 @@ -1005,9 +1118,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.NoContent: properties: @@ -1018,8 +1131,8 @@ definitions: example: success type: string required: - - message - - status + - message + - status type: object responses.NotFound: properties: @@ -1030,8 +1143,8 @@ definitions: example: error type: string required: - - message - - status + - message + - status type: object responses.OkString: properties: @@ -1044,14 +1157,14 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.PhoneAPIKeyResponse: properties: data: - $ref: "#/definitions/entities.PhoneAPIKey" + $ref: '#/definitions/entities.PhoneAPIKey' message: example: Request handled successfully type: string @@ -1059,15 +1172,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 @@ -1076,14 +1189,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 @@ -1091,15 +1204,47 @@ 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 + type: string + status: + example: success + type: string + required: + - data + - message + - status + type: object + responses.SendScheduleResponse: + properties: + data: + $ref: '#/definitions/entities.SendSchedule' + message: + example: Request handled successfully + type: string + status: + example: success + type: string + required: + - data + - message + - status + type: object + responses.SendSchedulesResponse: + properties: + data: + items: + $ref: '#/definitions/entities.SendSchedule' type: array message: example: Request handled successfully @@ -1108,9 +1253,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.Unauthorized: properties: @@ -1124,9 +1269,9 @@ definitions: example: error type: string required: - - data - - message - - status + - data + - message + - status type: object responses.UnprocessableEntity: properties: @@ -1143,14 +1288,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 @@ -1158,9 +1303,9 @@ definitions: example: success type: string required: - - data - - message - - status + - data + - message + - status type: object responses.UserSubscriptionPaymentsResponse: properties: @@ -1223,42 +1368,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: @@ -1268,14 +1413,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 @@ -1283,15 +1428,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 @@ -1300,17 +1445,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 @@ -1321,1660 +1465,1857 @@ 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: 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 + 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 + produces: + - application/json + 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' + security: + - ApiKeyAuth: [] + summary: Get a message from the database. + tags: + - 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' "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: Lists the send schedules owned by the authenticated user. + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.SendSchedulesResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/responses.Unauthorized' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.InternalServerError' + security: + - ApiKeyAuth: [] + summary: List send schedules + tags: + - Send Schedules + post: + consumes: + - application/json + description: Creates a send schedule for the authenticated user. + parameters: + - description: Payload of new send schedule. + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.SendScheduleStore' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/responses.SendScheduleResponse' + "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' + security: + - ApiKeyAuth: [] + summary: Create send schedule + tags: + - Send Schedules + /send-schedules/{scheduleID}: + delete: + description: Deletes a send schedule owned by the authenticated user. + parameters: + - description: Schedule ID + in: path + name: scheduleID + required: true + type: string + produces: + - application/json + 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' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.InternalServerError' + security: + - ApiKeyAuth: [] + summary: Delete send schedule + tags: + - Send Schedules + get: + description: Loads a single send schedule owned by the authenticated user. + parameters: + - description: Schedule ID + in: path + name: scheduleID + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.SendScheduleResponse' + "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' + security: + - ApiKeyAuth: [] + summary: Show send schedule + tags: + - Send Schedules + put: + consumes: + - application/json + description: Updates 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.SendScheduleStore' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.SendScheduleResponse' + "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' + security: + - ApiKeyAuth: [] + summary: Update send schedule + tags: + - Send Schedules /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: Generate subscription payment invoice parameters + in: body + name: payload + required: true + schema: + $ref: '#/definitions/requests.UserPaymentInvoice' produces: - - application/pdf + - application/pdf responses: "200": description: OK @@ -2983,235 +3324,234 @@ 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 /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/pkg/di/container.go b/api/pkg/di/container.go index 14fc9ea4..e8c015d8 100644 --- a/api/pkg/di/container.go +++ b/api/pkg/di/container.go @@ -127,6 +127,7 @@ func NewContainer(projectID string, version string) (container *Container) { container.RegisterHeartbeatListeners() container.RegisterUserRoutes() + container.RegisterSendScheduleRoutes() container.RegisterUserListeners() container.RegisterPhoneRoutes() @@ -361,6 +362,10 @@ ALTER TABLE discords ADD CONSTRAINT IF NOT EXISTS uni_discords_server_id CHECK ( container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.User{}))) } + if err = db.AutoMigrate(&entities.SendSchedule{}); err != nil { + container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.SendSchedule{}))) + } + if err = db.AutoMigrate(&entities.Phone{}); err != nil { container.logger.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot migrate %T", &entities.Phone{}))) } @@ -750,6 +755,46 @@ func (container *Container) PhoneRepository() (repository repositories.PhoneRepo ) } +// SendScheduleRepository creates a new instance of repositories.SendScheduleRepository +func (container *Container) SendScheduleRepository() repositories.SendScheduleRepository { + container.logger.Debug("creating GORM repositories.SendScheduleRepository") + return repositories.NewGormSendScheduleRepository( + container.Logger(), + container.Tracer(), + container.DB(), + ) +} + +// SendScheduleService creates a new instance of services.SendScheduleService +func (container *Container) SendScheduleService() *services.SendScheduleService { + container.logger.Debug("creating services.SendScheduleService") + return services.NewSendScheduleService( + container.Logger(), + container.Tracer(), + container.SendScheduleRepository(), + ) +} + +// SendScheduleHandlerValidator creates a new instance of validators.SendScheduleHandlerValidator +func (container *Container) SendScheduleHandlerValidator() *validators.SendScheduleHandlerValidator { + container.logger.Debug("creating validators.SendScheduleHandlerValidator") + return validators.NewSendScheduleHandlerValidator( + container.Logger(), + container.Tracer(), + ) +} + +// SendScheduleHandler creates a new instance of handlers.SendScheduleHandler +func (container *Container) SendScheduleHandler() *handlers.SendScheduleHandler { + container.logger.Debug("creating handlers.SendScheduleHandler") + return handlers.NewSendScheduleHandler( + container.Logger(), + container.Tracer(), + container.SendScheduleHandlerValidator(), + container.SendScheduleService(), + ) +} + // BillingUsageRepository creates a new instance of repositories.BillingUsageRepository func (container *Container) BillingUsageRepository() (repository repositories.BillingUsageRepository) { container.logger.Debug("creating GORM repositories.BillingUsageRepository") @@ -1453,6 +1498,7 @@ func (container *Container) NotificationService() (service *services.PhoneNotifi container.FirebaseMessagingClient(), container.PhoneRepository(), container.PhoneNotificationRepository(), + container.SendScheduleRepository(), container.EventDispatcher(), ) } @@ -1508,6 +1554,12 @@ func (container *Container) RegisterUserRoutes() { container.UserHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware()) } +// RegisterSendScheduleRoutes registers routes for the /send-schedules prefix +func (container *Container) RegisterSendScheduleRoutes() { + container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.SendScheduleHandler{})) + container.SendScheduleHandler().RegisterRoutes(container.App(), container.AuthenticatedMiddleware()) +} + // RegisterEventRoutes registers routes for the /events prefix func (container *Container) RegisterEventRoutes() { container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.EventsHandler{})) diff --git a/api/pkg/entities/phone.go b/api/pkg/entities/phone.go index 83521759..80d2c8f2 100644 --- a/api/pkg/entities/phone.go +++ b/api/pkg/entities/phone.go @@ -8,12 +8,14 @@ import ( // Phone represents an android phone which has installed the http sms app type Phone struct { - ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` - UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"` - FcmToken *string `json:"fcm_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." validate:"optional"` - PhoneNumber string `json:"phone_number" example:"+18005550199"` - MessagesPerMinute uint `json:"messages_per_minute" example:"1"` - SIM SIM `json:"sim" gorm:"default:SIM1"` + ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` + UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"` + FcmToken *string `json:"fcm_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." validate:"optional"` + PhoneNumber string `json:"phone_number" example:"+18005550199"` + MessagesPerMinute uint `json:"messages_per_minute" example:"1"` + SIM SIM `json:"sim" gorm:"default:SIM1"` + ScheduleID *uuid.UUID `json:"schedule_id" gorm:"type:uuid" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` + Schedule *SendSchedule `json:"-" gorm:"foreignKey:ScheduleID;constraint:OnDelete:SET NULL"` // MaxSendAttempts determines how many times to retry sending an SMS message MaxSendAttempts uint `json:"max_send_attempts" example:"2"` diff --git a/api/pkg/entities/send_schedule.go b/api/pkg/entities/send_schedule.go new file mode 100644 index 00000000..55a8e07c --- /dev/null +++ b/api/pkg/entities/send_schedule.go @@ -0,0 +1,26 @@ +package entities + +import ( + "time" + + "github.com/google/uuid" +) + +// SendScheduleWindow represents a single availability window for a day of the week. +type SendScheduleWindow struct { + DayOfWeek int `json:"day_of_week" example:"1"` + StartMinute int `json:"start_minute" example:"540"` + EndMinute int `json:"end_minute" example:"1020"` +} + +// SendSchedule controls when a phone is allowed to send outgoing SMS messages. +type SendSchedule struct { + ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` + UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"` + Name string `json:"name" example:"Business Hours"` + Timezone string `json:"timezone" example:"Europe/Tallinn"` + IsActive bool `json:"is_active" gorm:"default:true" example:"true"` + Windows []SendScheduleWindow `json:"windows" gorm:"type:jsonb;serializer:json"` + CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"` + UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"` +} diff --git a/api/pkg/handlers/send_schedule_handler.go b/api/pkg/handlers/send_schedule_handler.go new file mode 100644 index 00000000..22b47deb --- /dev/null +++ b/api/pkg/handlers/send_schedule_handler.go @@ -0,0 +1,187 @@ +package handlers + +import ( + "fmt" + + "github.com/NdoleStudio/httpsms/pkg/requests" + "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" + "github.com/google/uuid" + "github.com/palantir/stacktrace" +) + +type SendScheduleHandler struct { + handler + logger telemetry.Logger + tracer telemetry.Tracer + validator *validators.SendScheduleHandlerValidator + service *services.SendScheduleService +} + +func NewSendScheduleHandler(logger telemetry.Logger, tracer telemetry.Tracer, validator *validators.SendScheduleHandlerValidator, service *services.SendScheduleService) *SendScheduleHandler { + return &SendScheduleHandler{logger: logger.WithService(fmt.Sprintf("%T", &SendScheduleHandler{})), tracer: tracer, validator: validator, service: service} +} + +func (h *SendScheduleHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) { + router.Get("/v1/send-schedules", h.computeRoute(middlewares, h.Index)...) + router.Post("/v1/send-schedules", h.computeRoute(middlewares, h.Store)...) + router.Get("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Show)...) + router.Put("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Update)...) + router.Delete("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Delete)...) +} + +// Index godoc +// @Summary List send schedules +// @Description Lists the send schedules owned by the authenticated user. +// @Security ApiKeyAuth +// @Tags Send Schedules +// @Produce json +// @Success 200 {object} responses.SendSchedulesResponse +// @Failure 401 {object} responses.Unauthorized +// @Failure 500 {object} responses.InternalServerError +// @Router /send-schedules [get] +func (h *SendScheduleHandler) Index(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + schedules, err := h.service.Index(ctx, h.userIDFomContext(c)) + if err != nil { + ctxLogger.Error(stacktrace.Propagate(err, "cannot list send schedules")) + return h.responseInternalServerError(c) + } + return h.responseOK(c, "send schedules fetched successfully", schedules) +} + +// Show godoc +// @Summary Show send schedule +// @Description Loads a single send schedule owned by the authenticated user. +// @Security ApiKeyAuth +// @Tags Send Schedules +// @Produce json +// @Param scheduleID path string true "Schedule ID" +// @Success 200 {object} responses.SendScheduleResponse +// @Failure 401 {object} responses.Unauthorized +// @Failure 404 {object} responses.NotFound +// @Failure 500 {object} responses.InternalServerError +// @Router /send-schedules/{scheduleID} [get] +func (h *SendScheduleHandler) Show(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + scheduleID, err := uuid.Parse(c.Params("scheduleID")) + if err != nil { + return h.responseBadRequest(c, err) + } + schedule, err := h.service.Load(ctx, h.userIDFomContext(c), scheduleID) + if err != nil { + ctxLogger.Error(stacktrace.Propagate(err, "cannot load send schedule")) + if stacktrace.GetCode(err) == 404 { + return h.responseNotFound(c, err.Error()) + } + return h.responseInternalServerError(c) + } + return h.responseOK(c, "send schedule fetched successfully", schedule) +} + +// Store godoc +// @Summary Create send schedule +// @Description Creates a send schedule for the authenticated user. +// @Security ApiKeyAuth +// @Tags Send Schedules +// @Accept json +// @Produce json +// @Param payload body requests.SendScheduleStore true "Payload of new send schedule." +// @Success 201 {object} responses.SendScheduleResponse +// @Failure 400 {object} responses.BadRequest +// @Failure 401 {object} responses.Unauthorized +// @Failure 422 {object} responses.UnprocessableEntity +// @Failure 500 {object} responses.InternalServerError +// @Router /send-schedules [post] +func (h *SendScheduleHandler) Store(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + var request requests.SendScheduleStore + if err := c.BodyParser(&request); err != nil { + return h.responseBadRequest(c, err) + } + request = request.Sanitize() + if errors := h.validator.ValidateStore(ctx, request); len(errors) != 0 { + ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("validation errors [%s], while storing send schedule [%+#v]", spew.Sdump(errors), request))) + return h.responseUnprocessableEntity(c, errors, "validation errors while saving send schedule") + } + schedule, err := h.service.Store(ctx, request.ToParams(h.userFromContext(c))) + if err != nil { + ctxLogger.Error(stacktrace.Propagate(err, "cannot create send schedule")) + return h.responseInternalServerError(c) + } + return h.responseCreated(c, "send schedule created successfully", schedule) +} + +// Update godoc +// @Summary Update send schedule +// @Description Updates a send schedule owned by the authenticated user. +// @Security ApiKeyAuth +// @Tags Send Schedules +// @Accept json +// @Produce json +// @Param scheduleID path string true "Schedule ID" +// @Param payload body requests.SendScheduleStore true "Payload of updated send schedule." +// @Success 200 {object} responses.SendScheduleResponse +// @Failure 400 {object} responses.BadRequest +// @Failure 401 {object} responses.Unauthorized +// @Failure 404 {object} responses.NotFound +// @Failure 422 {object} responses.UnprocessableEntity +// @Failure 500 {object} responses.InternalServerError +// @Router /send-schedules/{scheduleID} [put] +func (h *SendScheduleHandler) Update(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + scheduleID, err := uuid.Parse(c.Params("scheduleID")) + if err != nil { + return h.responseBadRequest(c, err) + } + var request requests.SendScheduleStore + if err = c.BodyParser(&request); err != nil { + return h.responseBadRequest(c, err) + } + request = request.Sanitize() + if errors := h.validator.ValidateStore(ctx, request); len(errors) != 0 { + return h.responseUnprocessableEntity(c, errors, "validation errors while updating send schedule") + } + schedule, err := h.service.Update(ctx, h.userIDFomContext(c), scheduleID, request.ToParams(h.userFromContext(c))) + if err != nil { + ctxLogger.Error(stacktrace.Propagate(err, "cannot update send schedule")) + if stacktrace.GetCode(err) == 404 { + return h.responseNotFound(c, err.Error()) + } + return h.responseInternalServerError(c) + } + return h.responseOK(c, "send schedule updated successfully", schedule) +} + +// Delete godoc +// @Summary Delete send schedule +// @Description Deletes a send schedule owned by the authenticated user. +// @Security ApiKeyAuth +// @Tags Send Schedules +// @Produce json +// @Param scheduleID path string true "Schedule ID" +// @Success 204 {object} responses.NoContent +// @Failure 400 {object} responses.BadRequest +// @Failure 401 {object} responses.Unauthorized +// @Failure 500 {object} responses.InternalServerError +// @Router /send-schedules/{scheduleID} [delete] +func (h *SendScheduleHandler) Delete(c *fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + scheduleID, err := uuid.Parse(c.Params("scheduleID")) + if err != nil { + return h.responseBadRequest(c, err) + } + if err = h.service.Delete(ctx, h.userIDFomContext(c), scheduleID); err != nil { + ctxLogger.Error(stacktrace.Propagate(err, "cannot delete send schedule")) + return h.responseInternalServerError(c) + } + return h.responseNoContent(c, "send schedule deleted successfully") +} diff --git a/api/pkg/repositories/gorm_phone_notification_repository.go b/api/pkg/repositories/gorm_phone_notification_repository.go index f491e4b3..9a1d29d1 100644 --- a/api/pkg/repositories/gorm_phone_notification_repository.go +++ b/api/pkg/repositories/gorm_phone_notification_repository.go @@ -66,11 +66,12 @@ func (repository *gormPhoneNotificationRepository) UpdateStatus(ctx context.Cont } // Schedule a notification to be sent in the future -func (repository *gormPhoneNotificationRepository) Schedule(ctx context.Context, messagesPerMinute uint, notification *entities.PhoneNotification) error { +func (repository *gormPhoneNotificationRepository) Schedule(ctx context.Context, messagesPerMinute uint, schedule *entities.SendSchedule, notification *entities.PhoneNotification) error { ctx, span := repository.tracer.Start(ctx) defer span.End() if messagesPerMinute == 0 { + notification.ScheduledAt = repository.resolveScheduledAt(time.Now().UTC(), schedule) return repository.insert(ctx, notification) } @@ -86,12 +87,10 @@ func (repository *gormPhoneNotificationRepository) Schedule(ctx context.Context, return stacktrace.Propagate(err, msg) } - notification.ScheduledAt = time.Now().UTC() + notification.ScheduledAt = repository.resolveScheduledAt(time.Now().UTC(), schedule) if err == nil { - notification.ScheduledAt = repository.maxTime( - time.Now().UTC(), - lastNotification.ScheduledAt.Add(time.Duration(60/messagesPerMinute)*time.Second), - ) + rateLimitedAt := lastNotification.ScheduledAt.Add(time.Duration(60/messagesPerMinute) * time.Second) + notification.ScheduledAt = repository.resolveScheduledAt(repository.maxTime(notification.ScheduledAt, rateLimitedAt), schedule) } if err = tx.WithContext(ctx).Create(notification).Error; err != nil { @@ -108,6 +107,56 @@ func (repository *gormPhoneNotificationRepository) Schedule(ctx context.Context, return nil } +func (repository *gormPhoneNotificationRepository) resolveScheduledAt(current time.Time, schedule *entities.SendSchedule) time.Time { + if schedule == nil || !schedule.IsActive || len(schedule.Windows) == 0 { + return current.UTC() + } + + location, err := time.LoadLocation(schedule.Timezone) + if err != nil { + return current.UTC() + } + + base := current.In(location) + var best time.Time + for dayOffset := 0; dayOffset <= 7; dayOffset++ { + day := base.AddDate(0, 0, dayOffset) + weekday := int(day.Weekday()) + for _, window := range schedule.Windows { + if window.DayOfWeek != weekday { + continue + } + + start := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, location).Add(time.Duration(window.StartMinute) * time.Minute) + end := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, location).Add(time.Duration(window.EndMinute) * time.Minute) + + var candidate time.Time + switch { + case dayOffset == 0 && base.Before(start): + candidate = start + case dayOffset == 0 && (base.Equal(start) || (base.After(start) && base.Before(end))): + candidate = base + case dayOffset > 0: + candidate = start + default: + continue + } + + if best.IsZero() || candidate.Before(best) { + best = candidate + } + } + if !best.IsZero() { + break + } + } + + if best.IsZero() { + return current.UTC() + } + return best.UTC() +} + func (repository *gormPhoneNotificationRepository) maxTime(a, b time.Time) time.Time { if a.Unix() > b.Unix() { return a diff --git a/api/pkg/repositories/gorm_send_schedule_repository.go b/api/pkg/repositories/gorm_send_schedule_repository.go new file mode 100644 index 00000000..83405d40 --- /dev/null +++ b/api/pkg/repositories/gorm_send_schedule_repository.go @@ -0,0 +1,83 @@ +package repositories + +import ( + "context" + "errors" + "fmt" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/NdoleStudio/httpsms/pkg/telemetry" + "github.com/google/uuid" + "github.com/palantir/stacktrace" + "gorm.io/gorm" +) + +type gormSendScheduleRepository struct { + logger telemetry.Logger + tracer telemetry.Tracer + db *gorm.DB +} + +func NewGormSendScheduleRepository(logger telemetry.Logger, tracer telemetry.Tracer, db *gorm.DB) SendScheduleRepository { + return &gormSendScheduleRepository{logger: logger.WithService(fmt.Sprintf("%T", &gormSendScheduleRepository{})), tracer: tracer, db: db} +} + +func (r *gormSendScheduleRepository) Store(ctx context.Context, schedule *entities.SendSchedule) error { + ctx, span := r.tracer.Start(ctx) + defer span.End() + if err := r.db.WithContext(ctx).Create(schedule).Error; err != nil { + return r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot store send schedule [%s]", schedule.ID))) + } + return nil +} + +func (r *gormSendScheduleRepository) Update(ctx context.Context, schedule *entities.SendSchedule) error { + ctx, span := r.tracer.Start(ctx) + defer span.End() + if err := r.db.WithContext(ctx).Save(schedule).Error; err != nil { + return r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot update send schedule [%s]", schedule.ID))) + } + return nil +} + +func (r *gormSendScheduleRepository) Load(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) (*entities.SendSchedule, error) { + ctx, span := r.tracer.Start(ctx) + defer span.End() + item := new(entities.SendSchedule) + err := r.db.WithContext(ctx).Where("user_id = ?", userID).Where("id = ?", scheduleID).First(item).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, r.tracer.WrapErrorSpan(span, stacktrace.PropagateWithCode(err, ErrCodeNotFound, fmt.Sprintf("send schedule [%s] not found", scheduleID))) + } + if err != nil { + return nil, r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot load send schedule [%s]", scheduleID))) + } + return item, nil +} + +func (r *gormSendScheduleRepository) Index(ctx context.Context, userID entities.UserID) ([]entities.SendSchedule, error) { + ctx, span := r.tracer.Start(ctx) + defer span.End() + items := make([]entities.SendSchedule, 0) + if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Order("created_at ASC").Find(&items).Error; err != nil { + return nil, r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot index send schedules for user [%s]", userID))) + } + return items, nil +} + +func (r *gormSendScheduleRepository) Delete(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) error { + ctx, span := r.tracer.Start(ctx) + defer span.End() + if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Where("id = ?", scheduleID).Delete(&entities.SendSchedule{}).Error; err != nil { + return r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot delete send schedule [%s]", scheduleID))) + } + return nil +} + +func (r *gormSendScheduleRepository) DeleteAllForUser(ctx context.Context, userID entities.UserID) error { + ctx, span := r.tracer.Start(ctx) + defer span.End() + if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Delete(&entities.SendSchedule{}).Error; err != nil { + return r.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot delete send schedules for user [%s]", userID))) + } + return nil +} diff --git a/api/pkg/repositories/phone_notification_repository.go b/api/pkg/repositories/phone_notification_repository.go index 87f78490..c547379e 100644 --- a/api/pkg/repositories/phone_notification_repository.go +++ b/api/pkg/repositories/phone_notification_repository.go @@ -11,7 +11,7 @@ import ( // PhoneNotificationRepository loads and persists an entities.PhoneNotification type PhoneNotificationRepository interface { // Schedule a new entities.PhoneNotification - Schedule(ctx context.Context, messagesPerMinute uint, notification *entities.PhoneNotification) error + Schedule(ctx context.Context, messagesPerMinute uint, schedule *entities.SendSchedule, notification *entities.PhoneNotification) error // UpdateStatus of a notification UpdateStatus(ctx context.Context, notificationID uuid.UUID, status entities.PhoneNotificationStatus) error diff --git a/api/pkg/repositories/send_schedule_repository.go b/api/pkg/repositories/send_schedule_repository.go new file mode 100644 index 00000000..7b232d13 --- /dev/null +++ b/api/pkg/repositories/send_schedule_repository.go @@ -0,0 +1,18 @@ +package repositories + +import ( + "context" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/google/uuid" +) + +// SendScheduleRepository loads and persists entities.SendSchedule. +type SendScheduleRepository interface { + Store(ctx context.Context, schedule *entities.SendSchedule) error + Update(ctx context.Context, schedule *entities.SendSchedule) error + Load(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) (*entities.SendSchedule, error) + Index(ctx context.Context, userID entities.UserID) ([]entities.SendSchedule, error) + Delete(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) error + DeleteAllForUser(ctx context.Context, userID entities.UserID) error +} diff --git a/api/pkg/requests/phone_update_request.go b/api/pkg/requests/phone_update_request.go index f920fad4..bb710024 100644 --- a/api/pkg/requests/phone_update_request.go +++ b/api/pkg/requests/phone_update_request.go @@ -4,6 +4,8 @@ import ( "strings" "time" + "github.com/google/uuid" + "github.com/nyaruka/phonenumbers" "github.com/NdoleStudio/httpsms/pkg/entities" @@ -28,6 +30,8 @@ type PhoneUpsert struct { // SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot SIM string `json:"sim" example:"SIM1"` + + ScheduleID *string `json:"schedule_id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` } // Sanitize sets defaults to MessageOutstanding @@ -69,6 +73,13 @@ func (input *PhoneUpsert) ToUpsertParams(user entities.AuthContext, source strin maxSendAttempts = &input.MaxSendAttempts } + var scheduleID *uuid.UUID + if input.ScheduleID != nil && strings.TrimSpace(*input.ScheduleID) != "" { + if parsed, err := uuid.Parse(strings.TrimSpace(*input.ScheduleID)); err == nil { + scheduleID = &parsed + } + } + return &services.PhoneUpsertParams{ Source: source, PhoneNumber: phone, @@ -79,5 +90,6 @@ func (input *PhoneUpsert) ToUpsertParams(user entities.AuthContext, source strin FcmToken: fcmToken, UserID: user.ID, SIM: entities.SIM(input.SIM), + ScheduleID: scheduleID, } } diff --git a/api/pkg/requests/send_schedule_store_request.go b/api/pkg/requests/send_schedule_store_request.go new file mode 100644 index 00000000..5d9f06b0 --- /dev/null +++ b/api/pkg/requests/send_schedule_store_request.go @@ -0,0 +1,48 @@ +package requests + +import ( + "sort" + "strings" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/NdoleStudio/httpsms/pkg/services" +) + +type SendScheduleWindow struct { + DayOfWeek int `json:"day_of_week"` + StartMinute int `json:"start_minute"` + EndMinute int `json:"end_minute"` +} + +type SendScheduleStore struct { + request + Name string `json:"name"` + Timezone string `json:"timezone"` + IsActive bool `json:"is_active"` + Windows []SendScheduleWindow `json:"windows"` +} + +func (input *SendScheduleStore) Sanitize() SendScheduleStore { + input.Name = strings.TrimSpace(input.Name) + input.Timezone = strings.TrimSpace(input.Timezone) + windows := make([]SendScheduleWindow, 0, len(input.Windows)) + for _, item := range input.Windows { + windows = append(windows, SendScheduleWindow{DayOfWeek: item.DayOfWeek, StartMinute: item.StartMinute, EndMinute: item.EndMinute}) + } + sort.SliceStable(windows, func(i, j int) bool { + if windows[i].DayOfWeek == windows[j].DayOfWeek { + return windows[i].StartMinute < windows[j].StartMinute + } + return windows[i].DayOfWeek < windows[j].DayOfWeek + }) + input.Windows = windows + return *input +} + +func (input *SendScheduleStore) ToParams(user entities.AuthContext) *services.SendScheduleUpsertParams { + windows := make([]entities.SendScheduleWindow, 0, len(input.Windows)) + for _, item := range input.Windows { + windows = append(windows, entities.SendScheduleWindow{DayOfWeek: item.DayOfWeek, StartMinute: item.StartMinute, EndMinute: item.EndMinute}) + } + return &services.SendScheduleUpsertParams{UserID: user.ID, Name: input.Name, Timezone: input.Timezone, IsActive: input.IsActive, Windows: windows} +} diff --git a/api/pkg/responses/send_schedule_responses.go b/api/pkg/responses/send_schedule_responses.go new file mode 100644 index 00000000..405b4617 --- /dev/null +++ b/api/pkg/responses/send_schedule_responses.go @@ -0,0 +1,13 @@ +package responses + +import "github.com/NdoleStudio/httpsms/pkg/entities" + +type SendSchedulesResponse struct { + response + Data []entities.SendSchedule `json:"data"` +} + +type SendScheduleResponse struct { + response + Data entities.SendSchedule `json:"data"` +} diff --git a/api/pkg/services/phone_notification_service.go b/api/pkg/services/phone_notification_service.go index 7907d6d6..e8b7e013 100644 --- a/api/pkg/services/phone_notification_service.go +++ b/api/pkg/services/phone_notification_service.go @@ -24,6 +24,7 @@ type PhoneNotificationService struct { tracer telemetry.Tracer phoneNotificationRepository repositories.PhoneNotificationRepository phoneRepository repositories.PhoneRepository + sendScheduleRepository repositories.SendScheduleRepository messagingClient *messaging.Client eventDispatcher *EventDispatcher } @@ -35,6 +36,7 @@ func NewNotificationService( messagingClient *messaging.Client, phoneRepository repositories.PhoneRepository, phoneNotificationRepository repositories.PhoneNotificationRepository, + sendScheduleRepository repositories.SendScheduleRepository, dispatcher *EventDispatcher, ) (s *PhoneNotificationService) { return &PhoneNotificationService{ @@ -43,6 +45,7 @@ func NewNotificationService( messagingClient: messagingClient, phoneNotificationRepository: phoneNotificationRepository, phoneRepository: phoneRepository, + sendScheduleRepository: sendScheduleRepository, eventDispatcher: dispatcher, } } @@ -178,7 +181,19 @@ func (service *PhoneNotificationService) Schedule(ctx context.Context, params *P UpdatedAt: time.Now().UTC(), } - if err = service.phoneNotificationRepository.Schedule(ctx, phone.MessagesPerMinute, notification); err != nil { + var schedule *entities.SendSchedule + if phone.ScheduleID != nil { + schedule, err = service.sendScheduleRepository.Load(ctx, params.UserID, *phone.ScheduleID) + if err != nil && stacktrace.GetCode(err) != repositories.ErrCodeNotFound { + msg := fmt.Sprintf("cannot load send schedule [%s] for phone [%s]", *phone.ScheduleID, phone.ID) + return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)) + } + if stacktrace.GetCode(err) == repositories.ErrCodeNotFound { + schedule = nil + } + } + + if err = service.phoneNotificationRepository.Schedule(ctx, phone.MessagesPerMinute, schedule, notification); err != nil { msg := fmt.Sprintf("cannot schedule notification for message [%s] to phone [%s]", params.MessageID, phone.ID) return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)) } diff --git a/api/pkg/services/phone_service.go b/api/pkg/services/phone_service.go index df8e2104..9fa01ab0 100644 --- a/api/pkg/services/phone_service.go +++ b/api/pkg/services/phone_service.go @@ -91,6 +91,7 @@ type PhoneUpsertParams struct { MessageExpirationDuration *time.Duration MissedCallAutoReply *string SIM entities.SIM + ScheduleID *uuid.UUID Source string UserID entities.UserID } @@ -111,6 +112,7 @@ func (service *PhoneService) Upsert(ctx context.Context, params *PhoneUpsertPara UserID: params.UserID, FcmToken: params.FcmToken, SIM: params.SIM, + ScheduleID: params.ScheduleID, }) } @@ -132,6 +134,7 @@ func (service *PhoneService) Upsert(ctx context.Context, params *PhoneUpsertPara UserID: params.UserID, FcmToken: params.FcmToken, SIM: params.SIM, + ScheduleID: params.ScheduleID, }) } @@ -207,6 +210,7 @@ type PhoneFCMTokenParams struct { UserID entities.UserID FcmToken *string SIM entities.SIM + ScheduleID *uuid.UUID } // UpsertFCMToken the FCM token for an entities.Phone @@ -251,6 +255,7 @@ func (service *PhoneService) createPhone(ctx context.Context, params *PhoneFCMTo MaxSendAttempts: 2, SIM: params.SIM, MissedCallAutoReply: nil, + ScheduleID: params.ScheduleID, PhoneNumber: phonenumbers.Format(params.PhoneNumber, phonenumbers.E164), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(), @@ -294,6 +299,7 @@ func (service *PhoneService) update(phone *entities.Phone, params *PhoneUpsertPa } phone.SIM = params.SIM + phone.ScheduleID = params.ScheduleID return phone } diff --git a/api/pkg/services/send_schedule_service.go b/api/pkg/services/send_schedule_service.go new file mode 100644 index 00000000..96a72151 --- /dev/null +++ b/api/pkg/services/send_schedule_service.go @@ -0,0 +1,136 @@ +package services + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/NdoleStudio/httpsms/pkg/repositories" + "github.com/NdoleStudio/httpsms/pkg/telemetry" + "github.com/google/uuid" + "github.com/palantir/stacktrace" +) + +type SendScheduleService struct { + service + logger telemetry.Logger + tracer telemetry.Tracer + repository repositories.SendScheduleRepository +} + +func NewSendScheduleService(logger telemetry.Logger, tracer telemetry.Tracer, repository repositories.SendScheduleRepository) *SendScheduleService { + return &SendScheduleService{logger: logger.WithService(fmt.Sprintf("%T", &SendScheduleService{})), tracer: tracer, repository: repository} +} + +type SendScheduleUpsertParams struct { + UserID entities.UserID + Name string + Timezone string + IsActive bool + Windows []entities.SendScheduleWindow +} + +func (service *SendScheduleService) Index(ctx context.Context, userID entities.UserID) ([]entities.SendSchedule, error) { + return service.repository.Index(ctx, userID) +} + +func (service *SendScheduleService) Load(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) (*entities.SendSchedule, error) { + return service.repository.Load(ctx, userID, scheduleID) +} + +func (service *SendScheduleService) Store(ctx context.Context, params *SendScheduleUpsertParams) (*entities.SendSchedule, error) { + ctx, span := service.tracer.Start(ctx) + defer span.End() + schedule := &entities.SendSchedule{ID: uuid.New(), UserID: params.UserID, Name: params.Name, Timezone: params.Timezone, IsActive: params.IsActive, Windows: sanitizeWindows(params.Windows), CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()} + if err := service.repository.Store(ctx, schedule); err != nil { + return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot store send schedule [%s]", schedule.ID))) + } + return schedule, nil +} + +func (service *SendScheduleService) Update(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID, params *SendScheduleUpsertParams) (*entities.SendSchedule, error) { + ctx, span := service.tracer.Start(ctx) + defer span.End() + schedule, err := service.repository.Load(ctx, userID, scheduleID) + if err != nil { + return nil, err + } + schedule.Name = params.Name + schedule.Timezone = params.Timezone + schedule.IsActive = params.IsActive + schedule.Windows = sanitizeWindows(params.Windows) + schedule.UpdatedAt = time.Now().UTC() + if err = service.repository.Update(ctx, schedule); err != nil { + return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot update send schedule [%s]", schedule.ID))) + } + return schedule, nil +} + +func (service *SendScheduleService) Delete(ctx context.Context, userID entities.UserID, scheduleID uuid.UUID) error { + return service.repository.Delete(ctx, userID, scheduleID) +} + +func (service *SendScheduleService) ResolveScheduledSendTime(ctx context.Context, schedule *entities.SendSchedule, current time.Time) (time.Time, error) { + ctx, span := service.tracer.Start(ctx) + defer span.End() + if schedule == nil || !schedule.IsActive || len(schedule.Windows) == 0 { + return current.UTC(), nil + } + location, err := time.LoadLocation(schedule.Timezone) + if err != nil { + return current.UTC(), service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot load location [%s]", schedule.Timezone))) + } + + base := current.In(location) + type candidate struct{ t time.Time } + best := time.Time{} + for dayOffset := 0; dayOffset <= 7; dayOffset++ { + day := base.AddDate(0, 0, dayOffset) + weekday := int(day.Weekday()) + for _, window := range schedule.Windows { + if window.DayOfWeek != weekday { + continue + } + start := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, location).Add(time.Duration(window.StartMinute) * time.Minute) + end := time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, location).Add(time.Duration(window.EndMinute) * time.Minute) + var candidateTime time.Time + switch { + case dayOffset == 0 && base.Before(start): + candidateTime = start + case (dayOffset == 0 && (base.Equal(start) || (base.After(start) && base.Before(end)))) || (dayOffset > 0): + candidateTime = base + if dayOffset > 0 { + candidateTime = start + } + default: + continue + } + if best.IsZero() || candidateTime.Before(best) { + best = candidateTime + } + } + if !best.IsZero() { + break + } + } + if best.IsZero() { + return current.UTC(), nil + } + return best.UTC(), nil +} + +func sanitizeWindows(windows []entities.SendScheduleWindow) []entities.SendScheduleWindow { + result := make([]entities.SendScheduleWindow, 0, len(windows)) + for _, item := range windows { + result = append(result, entities.SendScheduleWindow{DayOfWeek: item.DayOfWeek, StartMinute: item.StartMinute, EndMinute: item.EndMinute}) + } + sort.SliceStable(result, func(i, j int) bool { + if result[i].DayOfWeek == result[j].DayOfWeek { + return result[i].StartMinute < result[j].StartMinute + } + return result[i].DayOfWeek < result[j].DayOfWeek + }) + return result +} diff --git a/api/pkg/validators/phone_handler_validator.go b/api/pkg/validators/phone_handler_validator.go index 2369214e..f9c78255 100644 --- a/api/pkg/validators/phone_handler_validator.go +++ b/api/pkg/validators/phone_handler_validator.go @@ -88,6 +88,15 @@ func (validator *PhoneHandlerValidator) ValidateUpsert(_ context.Context, reques }) result := v.ValidateStruct() + if request.ScheduleID != nil && strings.TrimSpace(*request.ScheduleID) != "" { + if uuidErrors := validator.ValidateUUID(strings.TrimSpace(*request.ScheduleID), "schedule_id"); len(uuidErrors) > 0 { + for key, values := range uuidErrors { + for _, value := range values { + result.Add(key, value) + } + } + } + } if len(result) > 0 { return result } diff --git a/api/pkg/validators/send_schedule_handler_validator.go b/api/pkg/validators/send_schedule_handler_validator.go new file mode 100644 index 00000000..a199f398 --- /dev/null +++ b/api/pkg/validators/send_schedule_handler_validator.go @@ -0,0 +1,55 @@ +package validators + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/NdoleStudio/httpsms/pkg/requests" + "github.com/NdoleStudio/httpsms/pkg/telemetry" + "github.com/thedevsaddam/govalidator" +) + +type SendScheduleHandlerValidator struct { + validator + logger telemetry.Logger + tracer telemetry.Tracer +} + +func NewSendScheduleHandlerValidator(logger telemetry.Logger, tracer telemetry.Tracer) *SendScheduleHandlerValidator { + return &SendScheduleHandlerValidator{logger: logger.WithService(fmt.Sprintf("%T", &SendScheduleHandlerValidator{})), tracer: tracer} +} + +func (validator *SendScheduleHandlerValidator) ValidateStore(_ context.Context, request requests.SendScheduleStore) url.Values { + v := govalidator.New(govalidator.Options{ + Data: &request, + Rules: govalidator.MapData{ + "name": []string{"required", "min:2", "max:100"}, + "timezone": []string{"required", "min:2", "max:100"}, + }, + }) + result := v.ValidateStruct() + validator.validateWindows(result, request.Windows) + if _, err := time.LoadLocation(request.Timezone); err != nil { + result.Add("timezone", "timezone must be a valid IANA timezone") + } + return result +} + +func (validator *SendScheduleHandlerValidator) validateWindows(result url.Values, windows []requests.SendScheduleWindow) { + for index, item := range windows { + if item.DayOfWeek < 0 || item.DayOfWeek > 6 { + result.Add("windows", fmt.Sprintf("windows[%d].day_of_week must be between 0 and 6", index)) + } + if item.StartMinute < 0 || item.StartMinute > 1439 { + result.Add("windows", fmt.Sprintf("windows[%d].start_minute must be between 0 and 1439", index)) + } + if item.EndMinute < 1 || item.EndMinute > 1440 { + result.Add("windows", fmt.Sprintf("windows[%d].end_minute must be between 1 and 1440", index)) + } + if item.EndMinute <= item.StartMinute { + result.Add("windows", fmt.Sprintf("windows[%d].end_minute must be greater than start_minute", index)) + } + } +} diff --git a/web/pages/settings/index.vue b/web/pages/settings/index.vue index 6d3415d0..c6ef9474 100644 --- a/web/pages/settings/index.vue +++ b/web/pages/settings/index.vue @@ -171,6 +171,18 @@ + +
+ Manage availability schedules on a dedicated page and attach one + schedule to each phone. Outgoing messages will respect both the + selected schedule and the configured send rate. +
+
Webhooks allow us to send events to your server for example when
@@ -549,6 +561,18 @@
label="Max Send Attempts"
>
+
+ Create reusable availability schedules and attach them to phones
+ from the Settings page. Outgoing messages respect both the phone
+ send rate and the selected schedule.
+