{
  "openapi" : "3.0.3",
  "info" : {
    "version" : "2.0.0",
    "title" : "OwnBIT Development - SMS Gateway Service API",
    "description" : "Multi-tenant SMS API on top of a Teltonika TRB140 modem. Authenticate with an API key (`Authorization: Bearer sk_live_...` or `X-API-Key`) for integrations, or with a JWT from `POST /api/v1/auth/login` for people. Sending is asynchronous: `POST /api/v1/messages` queues the message and returns 202; follow the result with `GET /api/v1/messages/{id}` or a webhook. SMS are sent against a plan an administrator assigns to you (a package or pay-per-SMS) and pays in advance - see `GET /api/v1/plans` and `GET /api/v1/billing`.\n",
    "termsOfService" : "/terms-of-use",
    "contact" : {
      "name" : "Ioan Tiba",
      "email" : "ioantiba@ownbit.org",
      "url" : "https://www.ownbit.org"
    },
    "license" : {
      "name" : "Apache 2.0",
      "url" : "https://www.apache.org/licenses/LICENSE-2.0.html"
    }
  },
  "servers" : [ {
    "url" : "https://smsgw.ownbit.org",
    "description" : "This server"
  }, {
    "url" : "http://localhost:8400",
    "description" : "DEVELOPMENT-ENV"
  } ],
  "tags" : [ {
    "name" : "Authentication",
    "description" : "Get a token - log in as a person (email + password) or, for legacy machine clients, exchange a client key + secret."
  }, {
    "name" : "API Keys",
    "description" : "Create and revoke the API keys integrations authenticate with."
  }, {
    "name" : "Messages",
    "description" : "Queue SMS (single or batch), follow their status, cancel, estimate cost. Start here."
  }, {
    "name" : "Inbound Messages",
    "description" : "Read SMS received on the modem, attributed to your account."
  }, {
    "name" : "Verification",
    "description" : "One-time verification codes (send a code, check what the user typed)."
  }, {
    "name" : "Webhooks",
    "description" : "Signed event notifications to your HTTPS endpoints."
  }, {
    "name" : "Suppressions",
    "description" : "Numbers your account must not text (STOP replies and manual blocks)."
  }, {
    "name" : "Contacts & Groups",
    "description" : "Contacts you can send to, and the groups they are organised in."
  }, {
    "name" : "Templates",
    "description" : "Reusable message templates with {{variable}} placeholders."
  }, {
    "name" : "Billing",
    "description" : "Plans on offer, your plan and remaining SMS, SMS ledger, invoices and usage per period (JSON or CSV)."
  }, {
    "name" : "Users",
    "description" : "User accounts and your own profile / password."
  }, {
    "name" : "Administration",
    "description" : "Admin only - tenants, modems and their assignment to users, plans and plan assignment to users, invoices and payments, audit trail."
  }, {
    "name" : "Monitoring",
    "description" : "Service status, live TRB140 modem status (admin / machine clients only) and SMS statistics by country."
  }, {
    "name" : "Legacy",
    "description" : "Synchronous send, group send jobs, scheduled sends and per-user history. New integrations should use Messages."
  } ],
  "security" : [ {
    "BearerAuth" : [ ]
  }, {
    "ApiKeyAuth" : [ ]
  } ],
  "paths" : {
    "/api" : {
      "get" : {
        "description" : "Get API service status",
        "operationId" : "getApiStatus",
        "tags" : [ "Monitoring" ],
        "x-codegen-tag" : "Api",
        "security" : [ ],
        "responses" : {
          "200" : {
            "description" : "Service status",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1" : {
      "get" : {
        "description" : "Get API V1 service status",
        "operationId" : "getApiV1Status",
        "tags" : [ "Monitoring" ],
        "x-codegen-tag" : "Api",
        "security" : [ ],
        "responses" : {
          "200" : {
            "description" : "Service status",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/login" : {
      "post" : {
        "summary" : "Log in with a real user account",
        "description" : "Authenticate a user (email + password). Returns a Bearer JWT carrying the user's id and role, used for per-user scoped access to contacts/groups/messages.\n",
        "operationId" : "login",
        "tags" : [ "Authentication" ],
        "x-codegen-tag" : "UserAuth",
        "security" : [ ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/LoginRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Authentication successful — JWT token returned",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/TokenResponseDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Invalid credentials, or account disabled",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ErrorResponseDTO"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/users" : {
      "get" : {
        "summary" : "List all user accounts",
        "description" : "Admin-only.",
        "operationId" : "getUsers",
        "tags" : [ "Users" ],
        "x-codegen-tag" : "Users",
        "responses" : {
          "200" : {
            "description" : "User list",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UsersListDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          }
        }
      },
      "post" : {
        "summary" : "Create a new user account",
        "description" : "Admin-only — there is no public self-registration endpoint.",
        "operationId" : "createUser",
        "tags" : [ "Users" ],
        "x-codegen-tag" : "Users",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateUserRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "User created",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UserDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid request (e.g. missing/weak password)"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          },
          "409" : {
            "description" : "A user with this email already exists"
          }
        }
      }
    },
    "/api/v1/users/{id}" : {
      "put" : {
        "summary" : "Update a user's role, enabled status, or monthly quota",
        "description" : "Admin-only. Email/password are not editable here.",
        "operationId" : "updateUser",
        "tags" : [ "Users" ],
        "x-codegen-tag" : "Users",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/UpdateUserRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated user",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UserDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          },
          "404" : {
            "description" : "No such user"
          }
        }
      }
    },
    "/api/v1/users/me" : {
      "get" : {
        "summary" : "Get the current user's own profile",
        "operationId" : "getCurrentUser",
        "tags" : [ "Users" ],
        "x-codegen-tag" : "Users",
        "responses" : {
          "200" : {
            "description" : "Current user profile",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UserDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "404" : {
            "description" : "The authenticated user no longer exists"
          }
        }
      }
    },
    "/api/v1/contacts" : {
      "get" : {
        "description" : "Get all available contacts filtered by group",
        "operationId" : "getContacts",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Contacts",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/SearchContactsParametersDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Contacts list",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ContactsListDTO"
                }
              }
            }
          },
          "204" : {
            "description" : "No contacts found"
          },
          "401" : {
            "description" : "Unauthorized"
          }
        }
      },
      "post" : {
        "summary" : "Create a new contact",
        "operationId" : "createContact",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Contacts",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/ContactDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Successfully created"
          },
          "400" : {
            "description" : "Missing/invalid name, email, phone number, or country"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "409" : {
            "description" : "A contact with this name or email already exists"
          }
        }
      }
    },
    "/api/v1/contacts/{id}" : {
      "put" : {
        "summary" : "Update a contact",
        "description" : "Only the contact's owner (or ADMIN) may update it — see ContactService#canModify.",
        "operationId" : "updateContact",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Contacts",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/ContactDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated contact",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ContactDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Missing/invalid name, email, phone number, or country"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this contact"
          },
          "404" : {
            "description" : "No such contact"
          }
        }
      },
      "delete" : {
        "summary" : "Delete a contact",
        "description" : "Only the contact's owner (or ADMIN) may delete it.",
        "operationId" : "deleteContact",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Contacts",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Deleted"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this contact"
          },
          "404" : {
            "description" : "No such contact"
          }
        }
      }
    },
    "/api/v1/groups" : {
      "get" : {
        "description" : "Get all enabled groups",
        "operationId" : "getGroups",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Groups",
        "responses" : {
          "200" : {
            "description" : "Groups list",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/GroupsListDTO"
                }
              }
            }
          },
          "204" : {
            "description" : "No groups found"
          },
          "401" : {
            "description" : "Unauthorized"
          }
        }
      },
      "post" : {
        "summary" : "Create a new group",
        "operationId" : "createGroup",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Groups",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/GroupDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Successfully created"
          },
          "400" : {
            "description" : "Bad Request - group name is mandatory"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "409" : {
            "description" : "You already have a group with this name"
          }
        }
      }
    },
    "/api/v1/groups/{id}" : {
      "put" : {
        "summary" : "Update a group",
        "description" : "Only the group's owner (or ADMIN) may update it.",
        "operationId" : "updateGroup",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Groups",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/GroupDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated group",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/GroupDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Bad Request - group name is mandatory"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this group"
          },
          "404" : {
            "description" : "No such group"
          }
        }
      },
      "delete" : {
        "summary" : "Delete a group",
        "description" : "Only the group's owner (or ADMIN) may delete it.",
        "operationId" : "deleteGroup",
        "tags" : [ "Contacts & Groups" ],
        "x-codegen-tag" : "Groups",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Deleted"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this group"
          },
          "404" : {
            "description" : "No such group"
          }
        }
      }
    },
    "/api/v1/templates" : {
      "get" : {
        "summary" : "List message templates visible to the current caller",
        "operationId" : "getTemplates",
        "tags" : [ "Templates" ],
        "x-codegen-tag" : "Templates",
        "responses" : {
          "200" : {
            "description" : "Templates",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/MessageTemplateDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          }
        }
      },
      "post" : {
        "summary" : "Create a message template",
        "operationId" : "createTemplate",
        "tags" : [ "Templates" ],
        "x-codegen-tag" : "Templates",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateMessageTemplateRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Created",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/MessageTemplateDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Missing name or body"
          },
          "401" : {
            "description" : "Unauthorized"
          }
        }
      }
    },
    "/api/v1/templates/{id}" : {
      "put" : {
        "summary" : "Update a message template",
        "operationId" : "updateTemplate",
        "tags" : [ "Templates" ],
        "x-codegen-tag" : "Templates",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateMessageTemplateRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/MessageTemplateDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Missing name or body"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this template"
          },
          "404" : {
            "description" : "No such template"
          }
        }
      },
      "delete" : {
        "summary" : "Delete a message template",
        "operationId" : "deleteTemplate",
        "tags" : [ "Templates" ],
        "x-codegen-tag" : "Templates",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Deleted"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this template"
          },
          "404" : {
            "description" : "No such template"
          }
        }
      }
    },
    "/api/v1/send-sms" : {
      "post" : {
        "summary" : "Send an SMS message",
        "operationId" : "sendSMS",
        "tags" : [ "Legacy" ],
        "x-codegen-tag" : "Sms",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/SendSmsBody"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Message sent successfully",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid request parameters",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "402" : {
            "description" : "No active SMS plan (`PLAN_REQUIRED`) or the plan is used up (`PLAN_LIMIT_REACHED`)",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "429" : {
            "description" : "Too Many Requests — rate limit exceeded",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "500" : {
            "description" : "Internal Server Error",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SendSmsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/send-jobs/{id}" : {
      "get" : {
        "summary" : "Get the status/progress of an async group send",
        "operationId" : "getSendJob",
        "tags" : [ "Legacy" ],
        "x-codegen-tag" : "Sms",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Job status",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/GroupSendJobDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "404" : {
            "description" : "No such job"
          }
        }
      }
    },
    "/api/v1/scheduled-messages/{id}" : {
      "get" : {
        "summary" : "Get the status of a scheduled send",
        "operationId" : "getScheduledMessage",
        "tags" : [ "Legacy" ],
        "x-codegen-tag" : "Sms",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Scheduled message status",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ScheduledMessageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "404" : {
            "description" : "No such scheduled message"
          }
        }
      },
      "delete" : {
        "summary" : "Cancel a pending scheduled send",
        "description" : "Only PENDING scheduled messages can be cancelled — one already SENT/FAILED cannot be undone.",
        "operationId" : "cancelScheduledMessage",
        "tags" : [ "Legacy" ],
        "x-codegen-tag" : "Sms",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Cancelled"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not the owner of this scheduled message"
          },
          "404" : {
            "description" : "No such scheduled message"
          },
          "409" : {
            "description" : "Already processed (not PENDING) — cannot be cancelled"
          }
        }
      }
    },
    "/api/v1/messages/mine" : {
      "get" : {
        "summary" : "The current user's own sent-message history",
        "description" : "Up to the 50 most recent SENT messages attributed to the caller (ownerId), newest first. Empty (not 404) for an ADMIN with no personal send history.",
        "operationId" : "getMyMessages",
        "tags" : [ "Legacy" ],
        "x-codegen-tag" : "Messages",
        "responses" : {
          "200" : {
            "description" : "Message history",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/MessageHistoryDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "404" : {
            "description" : "The authenticated user no longer exists"
          }
        }
      }
    },
    "/api/v1/stats/sms" : {
      "get" : {
        "summary" : "Get SMS statistics grouped by country",
        "operationId" : "getSmsStats",
        "tags" : [ "Monitoring" ],
        "x-codegen-tag" : "Stats",
        "responses" : {
          "200" : {
            "description" : "SMS statistics per country",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SmsStatsDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          }
        }
      }
    },
    "/api/v1/admin/modems" : {
      "get" : {
        "summary" : "List the modems",
        "description" : "Admin-only. Every modem with the number of users assigned to it, its circuit-breaker state and the outcome of the last health check. The password is never returned.\n",
        "operationId" : "listModems",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "responses" : {
          "200" : {
            "description" : "Modems",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/ModemDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          }
        }
      },
      "post" : {
        "summary" : "Add a modem",
        "description" : "Admin-only. Registers a TRB140 (its REST address and login). The first modem added becomes the default one, used by every user without an assigned modem. The password is stored encrypted.\n",
        "operationId" : "createModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateModemRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Modem added",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid address (must be an http or https URL) or values out of range"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "409" : {
            "description" : "A modem with this name already exists"
          }
        }
      }
    },
    "/api/v1/admin/modems/{modemId}" : {
      "parameters" : [ {
        "name" : "modemId",
        "in" : "path",
        "required" : true,
        "schema" : {
          "type" : "string",
          "format" : "uuid"
        }
      } ],
      "get" : {
        "summary" : "Get a modem",
        "description" : "Admin-only.",
        "operationId" : "getModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "responses" : {
          "200" : {
            "description" : "The modem",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such modem"
          }
        }
      },
      "put" : {
        "summary" : "Change a modem",
        "description" : "Admin-only. Omitted fields stay as they are; omit `password` to keep the stored one. The default modem and a modem that still has users assigned cannot be disabled.\n",
        "operationId" : "updateModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/UpdateModemRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "The updated modem",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid address or values out of range"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such modem"
          },
          "409" : {
            "description" : "Name already used, or the modem is the default / has users assigned and cannot be disabled"
          }
        }
      },
      "delete" : {
        "summary" : "Delete a modem",
        "description" : "Admin-only. Only a modem that is not the default, has no users and never sent or received a message can be deleted; disable any other.\n",
        "operationId" : "deleteModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "responses" : {
          "204" : {
            "description" : "Deleted"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such modem"
          },
          "409" : {
            "description" : "The modem is still in use"
          }
        }
      }
    },
    "/api/v1/admin/modems/{modemId}/default" : {
      "post" : {
        "summary" : "Make a modem the default",
        "description" : "Admin-only. The default modem serves users without an assigned modem and machine clients. The modem must be enabled.\n",
        "operationId" : "setDefaultModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "parameters" : [ {
          "name" : "modemId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The new default modem",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such modem"
          },
          "409" : {
            "description" : "The modem is disabled"
          }
        }
      }
    },
    "/api/v1/admin/modems/{modemId}/status" : {
      "get" : {
        "summary" : "Live status of a modem",
        "description" : "Admin-only. Asks the device itself (signal, network registration, SIM state).",
        "operationId" : "getModemLiveStatus",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "parameters" : [ {
          "name" : "modemId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Modem status snapshot",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemStatusDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such modem"
          },
          "502" : {
            "description" : "The modem is unreachable or returned an error"
          }
        }
      }
    },
    "/api/v1/admin/users/{userId}/modem" : {
      "put" : {
        "summary" : "Assign a modem to a user",
        "description" : "Admin-only. The user sends (and their queued messages go out) through this modem from now on. A null `modemId` removes the assignment, so the user falls back to the default modem. Messages already sent keep the modem they went through.\n",
        "operationId" : "assignUserModem",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "ModemAdmin",
        "parameters" : [ {
          "name" : "userId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/AssignModemRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "The user with the new assignment",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UserDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such user or modem"
          },
          "409" : {
            "description" : "The modem is disabled"
          }
        }
      }
    },
    "/api/v1/modem/status" : {
      "get" : {
        "summary" : "Get the default modem's health and network status",
        "description" : "Live snapshot of the default modem from the gateway's GET /api/modems/status (signal, network registration, SIM/connection state). Use GET /api/v1/admin/modems/{modemId}/status for a specific modem.\n",
        "operationId" : "getModemStatus",
        "tags" : [ "Monitoring" ],
        "x-codegen-tag" : "Modem",
        "responses" : {
          "200" : {
            "description" : "Modem status snapshot",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ModemStatusDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "502" : {
            "description" : "Gateway unreachable or returned an error"
          },
          "503" : {
            "description" : "No modem is configured yet"
          }
        }
      }
    },
    "/api/v1/users/me/password" : {
      "put" : {
        "summary" : "Change the current user's own password",
        "description" : "Requires the current password. Not available to API keys.",
        "operationId" : "changePassword",
        "tags" : [ "Users" ],
        "x-codegen-tag" : "Users",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/ChangePasswordRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "204" : {
            "description" : "Password changed"
          },
          "400" : {
            "description" : "New password too short or identical to the current one"
          },
          "401" : {
            "description" : "Unauthorized, or the current password is wrong"
          },
          "403" : {
            "description" : "Caller is not a user account"
          }
        }
      }
    },
    "/api/v1/tenants" : {
      "get" : {
        "summary" : "List tenants",
        "description" : "Admin-only.",
        "operationId" : "getTenants",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "Tenants",
        "responses" : {
          "200" : {
            "description" : "Tenants",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/TenantDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          }
        }
      },
      "post" : {
        "summary" : "Create a tenant",
        "description" : "Admin-only. A tenant groups users, API keys and outbound traffic; assign users to it via POST/PUT /users (`tenantId`).",
        "operationId" : "createTenant",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "Tenants",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateTenantRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Tenant created",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/TenantDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          },
          "409" : {
            "description" : "A tenant with this name already exists"
          }
        }
      }
    },
    "/api/v1/tenants/{id}" : {
      "put" : {
        "summary" : "Rename or enable/disable a tenant",
        "description" : "Admin-only. Disabling a tenant makes all of its API keys stop authenticating immediately.",
        "operationId" : "updateTenant",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "Tenants",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/UpdateTenantRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated tenant",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/TenantDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden — ADMIN role required"
          },
          "404" : {
            "description" : "No such tenant"
          },
          "409" : {
            "description" : "A tenant with this name already exists"
          }
        }
      }
    },
    "/api/v1/api-keys" : {
      "get" : {
        "summary" : "List API keys",
        "description" : "A user sees the keys of their own tenant; ADMIN sees all. The secret is never returned again after creation.",
        "operationId" : "getApiKeys",
        "tags" : [ "API Keys" ],
        "x-codegen-tag" : "ApiKeys",
        "responses" : {
          "200" : {
            "description" : "API keys",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/ApiKeyDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not allowed for API keys"
          }
        }
      },
      "post" : {
        "summary" : "Create an API key",
        "description" : "Creates a key (`sk_live_...`) that authenticates as the caller (or, for ADMIN, as `userId`), limited to the requested scopes. The plaintext `key` is in the response **once** — only its hash is stored. Use it as `Authorization: Bearer sk_live_...` or `X-API-Key`. Keys can never be created or managed through another API key.\n",
        "operationId" : "createApiKey",
        "tags" : [ "API Keys" ],
        "x-codegen-tag" : "ApiKeys",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateApiKeyRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Key created — store `key` now, it cannot be retrieved later",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ApiKeyCreatedDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Unknown scope, user without a tenant, or expiry in the past"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not allowed for API keys"
          }
        }
      }
    },
    "/api/v1/api-keys/{id}" : {
      "delete" : {
        "summary" : "Revoke an API key",
        "operationId" : "revokeApiKey",
        "tags" : [ "API Keys" ],
        "x-codegen-tag" : "ApiKeys",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Revoked (idempotent)"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Not allowed for API keys"
          },
          "404" : {
            "description" : "No such key (or not in your tenant)"
          }
        }
      }
    },
    "/api/v1/messages" : {
      "post" : {
        "summary" : "Queue an SMS (asynchronous)",
        "description" : "Accepts the message into a durable queue and returns **202 immediately** with a message id; a dispatcher sends it at the modem's pace, ordered by `priority` and fair between tenants. Poll `GET /api/v1/messages/{id}` for the outcome. Send an `Idempotency-Key` header to make retries safe: repeating the same key + payload returns the original message (200) instead of sending a second SMS; reusing a key with a different payload is a 409.\n",
        "operationId" : "sendMessage",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "parameters" : [ {
          "name" : "Idempotency-Key",
          "in" : "header",
          "required" : false,
          "schema" : {
            "type" : "string",
            "maxLength" : 100
          },
          "description" : "Unique per request; remembered for 24 hours"
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/SendMessageRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "202" : {
            "description" : "Accepted and queued",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/OutboundMessageDTO"
                }
              }
            }
          },
          "200" : {
            "description" : "Idempotent replay — the message created by the earlier identical request",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/OutboundMessageDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid number, text, or time window",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:send` scope"
          },
          "402" : {
            "description" : "No active SMS plan (`errorCode` PLAN_REQUIRED) or the plan is used up (`errorCode` PLAN_LIMIT_REACHED)",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "409" : {
            "description" : "Idempotency-Key reused with a different request",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "422" : {
            "description" : "The recipient opted out of your messages (`errorCode` SUPPRESSED)",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "429" : {
            "description" : "Rate limit exceeded - wait `Retry-After` seconds (see the X-RateLimit-* headers)"
          },
          "503" : {
            "description" : "Queue full, or cannot be delivered before `validUntil` (`errorCode` QUEUE_CAPACITY) - retry after `Retry-After`",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          }
        }
      },
      "get" : {
        "summary" : "List queued/sent messages (newest first)",
        "description" : "Scoped to the caller's tenant. Cursor-paginated — pass the previous page's `nextCursor`.",
        "operationId" : "listMessages",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "parameters" : [ {
          "name" : "status",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "QUEUED", "SENDING", "SENT", "FAILED", "EXPIRED", "CANCELLED" ]
          }
        }, {
          "name" : "to",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string"
          }
        }, {
          "name" : "createdFrom",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "createdTo",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50,
            "minimum" : 1,
            "maximum" : 100
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "A page of messages",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/OutboundMessagePageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:read` scope"
          }
        }
      }
    },
    "/api/v1/messages/{id}" : {
      "get" : {
        "summary" : "Get one queued/sent message",
        "operationId" : "getOutboundMessage",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The message",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/OutboundMessageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:read` scope"
          },
          "404" : {
            "description" : "No such message (or not in your tenant)"
          }
        }
      },
      "delete" : {
        "summary" : "Cancel a message that has not started sending",
        "operationId" : "cancelOutboundMessage",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Cancelled"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:send` scope"
          },
          "404" : {
            "description" : "No such message (or not in your tenant)"
          },
          "409" : {
            "description" : "Already sending or finished — cannot be cancelled"
          }
        }
      }
    },
    "/api/v1/inbound-messages" : {
      "get" : {
        "summary" : "List received SMS (newest first)",
        "description" : "Messages received on the modem, attributed to the tenant that last texted that number. ADMIN also sees unattributed messages. Cursor-paginated.\n",
        "operationId" : "listInboundMessages",
        "tags" : [ "Inbound Messages" ],
        "x-codegen-tag" : "Inbound",
        "parameters" : [ {
          "name" : "from",
          "in" : "query",
          "required" : false,
          "description" : "Only messages from this sender",
          "schema" : {
            "type" : "string"
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50,
            "minimum" : 1,
            "maximum" : 100
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "A page of received messages",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/InboundMessagePageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:read` scope"
          }
        }
      }
    },
    "/api/v1/messages/batch" : {
      "post" : {
        "summary" : "Queue many SMS in one request (asynchronous)",
        "description" : "Up to 1000 messages. Each item is accepted or rejected independently - the response lists the outcome per item (`index` matches the request order). Always 202 when the request itself is well-formed, even if some items were rejected. `Idempotency-Key` applies to the whole batch (each item derives its own key from it), so retrying a timed-out batch is safe.\n",
        "operationId" : "sendBatch",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "parameters" : [ {
          "name" : "Idempotency-Key",
          "in" : "header",
          "required" : false,
          "schema" : {
            "type" : "string",
            "maxLength" : 90
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/SendBatchRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "202" : {
            "description" : "Processed - see per-item results",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/BatchResponseDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Empty batch, more than 1000 items, or malformed request",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "402" : {
            "description" : "No active SMS plan, or your plan cannot cover the whole batch - nothing was queued (`errorCode` PLAN_REQUIRED / PLAN_LIMIT_REACHED)",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:send` scope"
          },
          "429" : {
            "description" : "Rate limit exceeded"
          }
        }
      }
    },
    "/api/v1/messages/estimate" : {
      "post" : {
        "summary" : "Estimate segments (and cost) before sending",
        "operationId" : "estimateMessage",
        "tags" : [ "Messages" ],
        "x-codegen-tag" : "Outbound",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/EstimateRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Estimate",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/EstimateResponseDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid number or text",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "API key lacks the `sms:read` scope"
          }
        }
      }
    },
    "/api/v1/webhooks" : {
      "get" : {
        "summary" : "List webhook endpoints",
        "operationId" : "getWebhooks",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "responses" : {
          "200" : {
            "description" : "Endpoints (secrets are never listed)",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/WebhookDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          }
        }
      },
      "post" : {
        "summary" : "Register a webhook endpoint",
        "description" : "The `secret` in the response is shown **once**. Every delivery carries `X-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, \"<t>.<body>\")>` - reject deliveries whose timestamp is more than a few minutes old. Delivery is at-least-once: dedupe on the event `id`. The URL must be https and must not resolve to a private/internal address.\n",
        "operationId" : "createWebhook",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CreateWebhookRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Created",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookCreatedDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Unsafe/invalid URL, unknown event, or too many endpoints"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}" : {
      "put" : {
        "summary" : "Change URL, subscribed events, or enabled state",
        "operationId" : "updateWebhook",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/UpdateWebhookRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Unsafe/invalid URL or unknown event"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          },
          "404" : {
            "description" : "No such endpoint in your account"
          }
        }
      },
      "delete" : {
        "summary" : "Delete a webhook endpoint",
        "operationId" : "deleteWebhook",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Deleted"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          },
          "404" : {
            "description" : "No such endpoint in your account"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/test" : {
      "post" : {
        "summary" : "Send a signed `ping` event to the endpoint",
        "operationId" : "testWebhook",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "202" : {
            "description" : "Ping queued",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookDeliveryDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          },
          "404" : {
            "description" : "No such endpoint in your account"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries" : {
      "get" : {
        "summary" : "Recent deliveries to an endpoint (newest first)",
        "operationId" : "getWebhookDeliveries",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Deliveries",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookDeliveryPageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          },
          "404" : {
            "description" : "No such endpoint in your account"
          }
        }
      }
    },
    "/api/v1/webhook-deliveries/{id}/redeliver" : {
      "post" : {
        "summary" : "Re-queue a delivery (resets its attempts)",
        "operationId" : "redeliverWebhook",
        "tags" : [ "Webhooks" ],
        "x-codegen-tag" : "Webhooks",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "202" : {
            "description" : "Re-queued",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/WebhookDeliveryDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `webhooks:manage`"
          },
          "404" : {
            "description" : "No such delivery in your account"
          }
        }
      }
    },
    "/api/v1/suppressions" : {
      "get" : {
        "summary" : "Numbers your account must not text (opt-outs and manual blocks)",
        "operationId" : "getSuppressions",
        "tags" : [ "Suppressions" ],
        "x-codegen-tag" : "Suppressions",
        "parameters" : [ {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Suppressions",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SuppressionPageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:send`"
          }
        }
      },
      "post" : {
        "summary" : "Block a number manually",
        "operationId" : "addSuppression",
        "tags" : [ "Suppressions" ],
        "x-codegen-tag" : "Suppressions",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/AddSuppressionRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Blocked (idempotent)",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/SuppressionDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid number"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:send`"
          }
        }
      }
    },
    "/api/v1/suppressions/{id}" : {
      "delete" : {
        "summary" : "Unblock a number",
        "operationId" : "deleteSuppression",
        "tags" : [ "Suppressions" ],
        "x-codegen-tag" : "Suppressions",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "204" : {
            "description" : "Removed"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:send`"
          },
          "404" : {
            "description" : "Not in your account's list"
          }
        }
      }
    },
    "/api/v1/verify" : {
      "post" : {
        "summary" : "Send a one-time verification code",
        "description" : "Generates a numeric code, sends it with OTP priority (it expires unsent if it cannot go out before `ttlSeconds`) and returns a verification id. The code is never returned or stored in clear. Check what the user typed with `POST /api/v1/verify/check`.\n",
        "operationId" : "startVerification",
        "tags" : [ "Verification" ],
        "x-codegen-tag" : "Verify",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/StartVerificationRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "202" : {
            "description" : "Code queued",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/VerificationDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Invalid number/length/ttl/template",
            "content" : {
              "application/problem+json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ProblemDetailDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:send`"
          },
          "429" : {
            "description" : "A code was sent to this number moments ago (or rate limit exceeded)"
          }
        }
      }
    },
    "/api/v1/verify/check" : {
      "post" : {
        "summary" : "Check a submitted verification code",
        "description" : "Attempt-limited (5). Wrong codes return `valid=false` with the attempts left, never an error status.",
        "operationId" : "checkVerification",
        "tags" : [ "Verification" ],
        "x-codegen-tag" : "Verify",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/CheckVerificationRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Result",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/CheckVerificationResponseDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:send`"
          },
          "404" : {
            "description" : "No such verification in your account"
          }
        }
      }
    },
    "/api/v1/audit-log" : {
      "get" : {
        "summary" : "Administrative audit trail",
        "description" : "Admin-only. Newest first, cursor-paginated.",
        "operationId" : "getAuditLog",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "Audit",
        "parameters" : [ {
          "name" : "tenantId",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Audit entries",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/AuditLogPageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          }
        }
      }
    },
    "/api/v1/plans" : {
      "get" : {
        "summary" : "Public plan catalogue",
        "description" : "The plans on offer, excluding VAT. A `PACKAGE` is a fixed number of SMS for a fixed price; a `PER_SMS` plan charges `unitPrice` per SMS for a quantity chosen when it is assigned (from `minQuantity`). Plans do not expire. Plans are assigned to users by an administrator.\n",
        "operationId" : "listPlans",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Plans",
        "security" : [ ],
        "responses" : {
          "200" : {
            "description" : "Active plans that anyone can be given",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/PlanDTO"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/billing" : {
      "get" : {
        "summary" : "Your plan, SMS left and what you still have to pay",
        "description" : "`activePlan` is the plan you can send with; `pendingPlan` was assigned but its invoice is not paid yet (it becomes active when the invoice is paid, and its SMS are added to those you have left). `amountDue` is the gross total of your unpaid invoices. Without an active plan no SMS can be sent.\n",
        "operationId" : "getBilling",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Billing",
        "responses" : {
          "200" : {
            "description" : "Billing summary",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/BillingSummaryDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Machine client without a user account, or API key lacks `sms:read`"
          }
        }
      }
    },
    "/api/v1/billing/invoices" : {
      "get" : {
        "summary" : "Your invoices (newest first)",
        "operationId" : "getInvoices",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Billing",
        "parameters" : [ {
          "name" : "status",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "UNPAID", "PAID", "CANCELLED" ]
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Invoices",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/InvoicePageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Machine client without a user account, or API key lacks `sms:read`"
          }
        }
      }
    },
    "/api/v1/billing/invoices/{id}" : {
      "get" : {
        "summary" : "One of your invoices, with its lines",
        "operationId" : "getInvoice",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Billing",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The invoice",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/InvoiceDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Machine client without a user account, or API key lacks `sms:read`"
          },
          "404" : {
            "description" : "No such invoice in your account"
          }
        }
      }
    },
    "/api/v1/billing/ledger" : {
      "get" : {
        "summary" : "Every movement of your SMS balance (newest first)",
        "description" : "`CREDIT` when a plan is activated, `RESERVE` when a message is queued, `CAPTURE` when it was sent, `RELEASE` when it was not, `ADJUSTMENT` for manual corrections (signed).\n",
        "operationId" : "getSmsLedger",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Billing",
        "parameters" : [ {
          "name" : "type",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "CREDIT", "RESERVE", "CAPTURE", "RELEASE", "ADJUSTMENT" ]
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Ledger page",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UsageEntryPageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Machine client without a user account, or API key lacks `sms:read`"
          }
        }
      }
    },
    "/api/v1/billing/profile" : {
      "put" : {
        "summary" : "Details printed on your invoices",
        "description" : "Applies to your whole tenant and to invoices issued from now on. Needs a user login (not an API key).",
        "operationId" : "updateBillingProfile",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Billing",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/BillingProfileDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Saved",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/BillingProfileDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or an API key"
          }
        }
      }
    },
    "/api/v1/admin/plans" : {
      "get" : {
        "summary" : "Every plan, including inactive and administrator-only ones",
        "description" : "Admin-only.",
        "operationId" : "listAllPlans",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "responses" : {
          "200" : {
            "description" : "All plans",
            "content" : {
              "application/json" : {
                "schema" : {
                  "type" : "array",
                  "items" : {
                    "$ref" : "#/components/schemas/PlanDTO"
                  }
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          }
        }
      },
      "post" : {
        "summary" : "Create a plan",
        "description" : "Admin-only. `PACKAGE` needs `smsQuantity` and `priceNet`; `PER_SMS` needs `unitPrice` (and optionally `minQuantity`); `UNLIMITED` is always administrator-only.\n",
        "operationId" : "createPlan",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/PlanRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "Created",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Missing or inconsistent fields for the plan type"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "409" : {
            "description" : "The code is already used"
          }
        }
      }
    },
    "/api/v1/admin/plans/{id}" : {
      "put" : {
        "summary" : "Change a plan",
        "description" : "Admin-only. Replaces every editable field (not the code). Users who already hold the plan keep the terms they bought; the change applies to future assignments.\n",
        "operationId" : "updatePlan",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/PlanRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Updated",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Missing or inconsistent fields for the plan type"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such plan"
          }
        }
      },
      "delete" : {
        "summary" : "Deactivate a plan",
        "description" : "Admin-only. A plan is never deleted (assignments refer to it); it just can't be assigned any more.",
        "operationId" : "deactivatePlan",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The deactivated plan",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such plan"
          }
        }
      }
    },
    "/api/v1/admin/users/{userId}/plan" : {
      "post" : {
        "summary" : "Assign a plan to a user and invoice it",
        "description" : "Admin-only. Issues an UNPAID invoice for the plan (paid in advance); the plan becomes active when the invoice is marked paid, and its SMS are added to those the user has left. A newer assignment replaces one still waiting for payment (its invoice is cancelled). `quantity` is required for `PER_SMS` plans and not allowed otherwise. `UNLIMITED` can only be given to an administrator and is free, so it is active immediately.\n",
        "operationId" : "assignPlan",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "userId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/AssignPlanRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "201" : {
            "description" : "The assignment and its invoice",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanAssignmentDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Plan missing or inactive, not allowed for this user, or wrong quantity"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such user"
          }
        }
      }
    },
    "/api/v1/admin/users/{userId}/billing" : {
      "get" : {
        "summary" : "A user's billing summary, with every past assignment",
        "description" : "Admin-only.",
        "operationId" : "getUserBilling",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "userId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Billing summary",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/BillingSummaryDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such user"
          }
        }
      }
    },
    "/api/v1/admin/users/{userId}/usage-adjustments" : {
      "post" : {
        "summary" : "Correct a user's SMS balance",
        "description" : "Admin-only. Adds or removes SMS without an invoice; the balance never goes below zero.",
        "operationId" : "adjustUserUsage",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "userId",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/UsageAdjustmentRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "Billing summary after the correction",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/BillingSummaryDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Zero, out of range, or the user has no plan yet"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such user"
          }
        }
      }
    },
    "/api/v1/admin/invoices" : {
      "get" : {
        "summary" : "Invoices of all users (newest first)",
        "description" : "Admin-only. Filter by `userId` and/or `status`.",
        "operationId" : "listAllInvoices",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "userId",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "status",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "UNPAID", "PAID", "CANCELLED" ]
          }
        }, {
          "name" : "cursor",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        }, {
          "name" : "limit",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 50
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Invoices",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/InvoicePageDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          }
        }
      }
    },
    "/api/v1/admin/invoices/{id}" : {
      "get" : {
        "summary" : "Any invoice, with its lines",
        "description" : "Admin-only.",
        "operationId" : "getAnyInvoice",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The invoice",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/InvoiceDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such invoice"
          }
        }
      }
    },
    "/api/v1/admin/invoices/{id}/payment" : {
      "post" : {
        "summary" : "Record the payment of an invoice",
        "description" : "Admin-only. Marks the invoice PAID and activates its plan: the plan's SMS are added to the user's balance. Repeating it on a paid invoice changes nothing.\n",
        "operationId" : "markInvoicePaid",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "requestBody" : {
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/MarkInvoicePaidRequestDTO"
              }
            }
          }
        },
        "responses" : {
          "200" : {
            "description" : "The activated assignment and the paid invoice",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanAssignmentDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such invoice"
          },
          "409" : {
            "description" : "The invoice was cancelled"
          }
        }
      }
    },
    "/api/v1/admin/invoices/{id}/cancel" : {
      "post" : {
        "summary" : "Cancel an unpaid invoice",
        "description" : "Admin-only. Also cancels the plan assignment waiting for it. A paid invoice cannot be cancelled.",
        "operationId" : "cancelInvoice",
        "tags" : [ "Administration" ],
        "x-codegen-tag" : "BillingAdmin",
        "parameters" : [ {
          "name" : "id",
          "in" : "path",
          "required" : true,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "The cancelled assignment and invoice",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/PlanAssignmentDTO"
                }
              }
            }
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Forbidden - ADMIN role required"
          },
          "404" : {
            "description" : "No such invoice"
          },
          "409" : {
            "description" : "The invoice is already paid"
          }
        }
      }
    },
    "/api/v1/usage" : {
      "get" : {
        "summary" : "Messages, segments and cost per period",
        "description" : "Aggregates your outbound traffic. Defaults to the current month by day. Tenants see their own usage; ADMIN may pass `tenantId` (omit for the whole platform).\n",
        "operationId" : "getUsage",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Usage",
        "parameters" : [ {
          "name" : "from",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "to",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "granularity",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "hour", "day", "month" ],
            "default" : "day"
          }
        }, {
          "name" : "tenantId",
          "in" : "query",
          "required" : false,
          "description" : "ADMIN only",
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "Usage report",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/UsageDTO"
                }
              }
            }
          },
          "400" : {
            "description" : "Bad time range"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:read`"
          }
        }
      }
    },
    "/api/v1/usage/export" : {
      "get" : {
        "summary" : "The usage report as CSV",
        "operationId" : "exportUsage",
        "tags" : [ "Billing" ],
        "x-codegen-tag" : "Usage",
        "parameters" : [ {
          "name" : "from",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "to",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "date-time"
          }
        }, {
          "name" : "granularity",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "enum" : [ "hour", "day", "month" ],
            "default" : "day"
          }
        }, {
          "name" : "tenantId",
          "in" : "query",
          "required" : false,
          "schema" : {
            "type" : "string",
            "format" : "uuid"
          }
        } ],
        "responses" : {
          "200" : {
            "description" : "CSV with a header row",
            "content" : {
              "text/csv" : {
                "schema" : {
                  "type" : "string"
                }
              }
            }
          },
          "400" : {
            "description" : "Bad time range"
          },
          "401" : {
            "description" : "Unauthorized"
          },
          "403" : {
            "description" : "Caller has no tenant, or API key lacks `sms:read`"
          }
        }
      }
    }
  },
  "components" : {
    "securitySchemes" : {
      "BearerAuth" : {
        "type" : "http",
        "scheme" : "bearer",
        "bearerFormat" : "JWT",
        "description" : "A JWT from POST /api/v1/auth/login, or an API key (`sk_live_...`) created with POST /api/v1/api-keys - both go in the same Bearer header.\n"
      },
      "ApiKeyAuth" : {
        "type" : "apiKey",
        "in" : "header",
        "name" : "X-API-Key",
        "description" : "An API key, as an alternative to sending it as a Bearer token."
      }
    },
    "schemas" : {
      "TokenResponseDTO" : {
        "type" : "object",
        "description" : "Successful authentication response",
        "properties" : {
          "accessToken" : {
            "type" : "string",
            "description" : "JWT Bearer token to use in Authorization header"
          },
          "tokenType" : {
            "type" : "string",
            "description" : "Always \"Bearer\"",
            "example" : "Bearer"
          },
          "expiresIn" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Token validity in seconds"
          },
          "role" : {
            "type" : "string",
            "description" : "The authenticated user's role",
            "enum" : [ "ADMIN", "USER" ]
          }
        }
      },
      "LoginRequestDTO" : {
        "type" : "object",
        "required" : [ "email", "password" ],
        "description" : "Credentials for POST /api/v1/auth/login",
        "properties" : {
          "email" : {
            "type" : "string"
          },
          "password" : {
            "type" : "string"
          }
        }
      },
      "CreateUserRequestDTO" : {
        "type" : "object",
        "required" : [ "email", "password", "role" ],
        "description" : "Admin-only user creation request",
        "properties" : {
          "email" : {
            "type" : "string"
          },
          "password" : {
            "type" : "string",
            "description" : "Plaintext password — hashed server-side immediately, never stored or logged as-is"
          },
          "role" : {
            "type" : "string",
            "enum" : [ "ADMIN", "USER" ]
          },
          "monthlyQuota" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Soft monthly SMS send quota — omit/null for unlimited. Exceeding it only logs a warning, never blocks sends (see QuotaService)"
          },
          "tenantId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Tenant this user belongs to (required for the user to create API keys / send through the queue under a tenant)"
          }
        }
      },
      "UpdateUserRequestDTO" : {
        "type" : "object",
        "description" : "Admin-only user update — email/password are not editable here",
        "properties" : {
          "role" : {
            "type" : "string",
            "enum" : [ "ADMIN", "USER" ]
          },
          "enabled" : {
            "type" : "boolean"
          },
          "monthlyQuota" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Soft monthly SMS send quota — null clears it back to unlimited"
          },
          "tenantId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Move the user to this tenant — omit to leave unchanged"
          }
        }
      },
      "UserDTO" : {
        "type" : "object",
        "description" : "A user account (never includes the password hash)",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "email" : {
            "type" : "string"
          },
          "role" : {
            "type" : "string",
            "enum" : [ "ADMIN", "USER" ]
          },
          "enabled" : {
            "type" : "boolean"
          },
          "monthlyQuota" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Soft monthly SMS send quota — null means unlimited"
          },
          "tenantId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "modemId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "The modem this user sends through - null means the default modem"
          },
          "smsSentThisMonth" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS sent by this user so far this calendar month (computed at read time)"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "UsersListDTO" : {
        "type" : "array",
        "items" : {
          "$ref" : "#/components/schemas/UserDTO"
        }
      },
      "ErrorResponseDTO" : {
        "type" : "object",
        "description" : "Error response",
        "properties" : {
          "state" : {
            "type" : "string",
            "description" : "Machine-readable error code"
          },
          "message" : {
            "type" : "string",
            "description" : "Human-readable error description"
          }
        }
      },
      "SearchContactsParametersDTO" : {
        "type" : "object",
        "description" : "Parameters for filtering contacts",
        "properties" : {
          "group" : {
            "type" : "string"
          }
        }
      },
      "ContactsListDTO" : {
        "type" : "array",
        "items" : {
          "$ref" : "#/components/schemas/ContactDTO"
        }
      },
      "Pageable" : {
        "description" : "Pageable query parameters",
        "type" : "object",
        "properties" : {
          "page" : {
            "type" : "integer"
          },
          "size" : {
            "type" : "integer"
          }
        }
      },
      "ContactDTO" : {
        "type" : "object",
        "description" : "Contact object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "email" : {
            "type" : "string"
          },
          "phoneNumber" : {
            "type" : "string"
          },
          "notes" : {
            "type" : "string"
          },
          "enabled" : {
            "type" : "boolean"
          },
          "contactGroups" : {
            "type" : "array",
            "uniqueItems" : true,
            "items" : {
              "$ref" : "#/components/schemas/GroupDTO"
            }
          },
          "country" : {
            "$ref" : "#/components/schemas/CountryDTO"
          }
        }
      },
      "GroupsListDTO" : {
        "type" : "array",
        "items" : {
          "$ref" : "#/components/schemas/GroupDTO"
        }
      },
      "GroupDTO" : {
        "type" : "object",
        "description" : "Group object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "enabled" : {
            "type" : "boolean"
          }
        }
      },
      "CountryDTO" : {
        "type" : "object",
        "description" : "Country object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "iso" : {
            "type" : "string"
          },
          "name" : {
            "type" : "string"
          },
          "nicename" : {
            "type" : "string"
          },
          "iso3" : {
            "type" : "string"
          },
          "numcode" : {
            "type" : "integer",
            "format" : "int32"
          },
          "phonecode" : {
            "type" : "integer",
            "format" : "int32"
          }
        }
      },
      "SendSmsBody" : {
        "type" : "object",
        "description" : "SMS send request. Exactly one of `to`, `contactId`, `groupId` must be provided: `to` sends immediately to a raw phone number (original behaviour, unchanged); `contactId` resolves the contact's phone number and sends immediately, tagging the resulting message with the contact; `groupId` queues an async GroupSendJob (one item per enabled group member) and returns 202 with a `jobId` to poll via GET /api/v1/send-jobs/{id}, rather than blocking the request until every member is sent. Exactly one of `text` or `templateId` must be provided — a template is rendered (its `{{variable}}` placeholders substituted from `templateParams`) into the final text at request time. If `scheduledAt` is set, nothing is sent immediately: a ScheduledMessage is queued instead (with the already-resolved recipient and already-rendered text) and 202 is returned with a `scheduledMessageId` to track via GET /api/v1/scheduled-messages/{id}.\n",
        "properties" : {
          "to" : {
            "type" : "string",
            "description" : "Recipient phone number in international format (e.g. +40760123456)"
          },
          "contactId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Send to this contact's phone number instead of a raw `to`"
          },
          "groupId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Queue an async send to every enabled member of this group instead of a single recipient"
          },
          "text" : {
            "type" : "string",
            "description" : "Message text (max 320 characters) — mutually exclusive with templateId"
          },
          "templateId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Render this template's body (with templateParams) instead of a literal text — mutually exclusive with text"
          },
          "templateParams" : {
            "type" : "object",
            "additionalProperties" : {
              "type" : "string"
            },
            "description" : "Variable substitutions for templateId's {{placeholders}}"
          },
          "scheduledAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "If set, queue the (already-resolved/rendered) send for this future time instead of sending immediately"
          }
        }
      },
      "SendSmsResponse" : {
        "type" : "object",
        "description" : "SMS send response",
        "properties" : {
          "state" : {
            "type" : "string",
            "description" : "Machine-readable state: OK, ACCEPTED, CLIENT_ERROR, PLAN_REQUIRED, PLAN_LIMIT_REACHED, RATE_LIMIT, ERROR, SERVER_ERROR"
          },
          "message" : {
            "type" : "string",
            "description" : "Human-readable description of the result"
          },
          "jobId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Present only for state=ACCEPTED from a groupId send — poll GET /api/v1/send-jobs/{id} for progress"
          },
          "scheduledMessageId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Present only for state=ACCEPTED from a scheduledAt send — poll GET /api/v1/scheduled-messages/{id}"
          }
        }
      },
      "MessageTemplateDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "body" : {
            "type" : "string"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "updatedAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "CreateMessageTemplateRequestDTO" : {
        "type" : "object",
        "required" : [ "name", "body" ],
        "properties" : {
          "name" : {
            "type" : "string"
          },
          "body" : {
            "type" : "string",
            "description" : "Template body with {{variable}} placeholders"
          }
        }
      },
      "ScheduledMessageDTO" : {
        "type" : "object",
        "description" : "Status of a scheduled (not-yet-fired, or already-processed) send",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "to" : {
            "type" : "string"
          },
          "contactId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "groupId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "text" : {
            "type" : "string"
          },
          "scheduledAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING", "SENT", "FAILED", "CANCELLED" ]
          },
          "errorMessage" : {
            "type" : "string"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "processedAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "MessageHistoryDTO" : {
        "type" : "object",
        "description" : "One entry in a user's own sent-message history (GET /api/v1/messages/mine)",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "sender" : {
            "type" : "string",
            "description" : "The recipient number this message was sent to (field name mirrors the underlying SENT/RECEIVED-agnostic storage)"
          },
          "message" : {
            "type" : "string"
          },
          "messageState" : {
            "type" : "string",
            "enum" : [ "SENT", "RECEIVED", "READ" ]
          },
          "contactId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "smsUsed" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "SMS parts consumed for this send, if known"
          },
          "countryCode" : {
            "type" : "string"
          },
          "receivedTime" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "GroupSendJobDTO" : {
        "type" : "object",
        "description" : "Status/progress of an async group send",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "groupId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING", "PROCESSING", "COMPLETED", "COMPLETED_WITH_ERRORS" ]
          },
          "totalCount" : {
            "type" : "integer",
            "format" : "int32"
          },
          "sentCount" : {
            "type" : "integer",
            "format" : "int32"
          },
          "failedCount" : {
            "type" : "integer",
            "format" : "int32"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "completedAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "SmsStatsDTO" : {
        "type" : "object",
        "description" : "Aggregated SMS statistics",
        "properties" : {
          "totalSent" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Total SMS sent"
          },
          "totalReceived" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Total SMS received"
          },
          "byCountry" : {
            "type" : "array",
            "description" : "Statistics broken down by country",
            "items" : {
              "$ref" : "#/components/schemas/CountryStatDTO"
            }
          }
        }
      },
      "CountryStatDTO" : {
        "type" : "object",
        "description" : "SMS statistics for a single country",
        "properties" : {
          "countryCode" : {
            "type" : "string",
            "description" : "ISO 3166-1 alpha-2 country code or XX for unknown"
          },
          "countryName" : {
            "type" : "string",
            "description" : "Full country name in English"
          },
          "sentCount" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Number of SMS sent to this country"
          },
          "receivedCount" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Number of SMS received from this country"
          }
        }
      },
      "ModemStatusDTO" : {
        "type" : "object",
        "description" : "Health/state snapshot of the TRB140's physical modem, mapped from GET /api/modems/status on the gateway (a much larger raw payload — only the fields relevant to connectivity health are surfaced here).\n",
        "properties" : {
          "id" : {
            "type" : "string",
            "description" : "Modem/SIM identifier as used by the gateway API (e.g. \"3-1\")",
            "example" : "3-1"
          },
          "name" : {
            "type" : "string",
            "description" : "Human-readable modem name",
            "example" : "Internal modem"
          },
          "model" : {
            "type" : "string",
            "example" : "EC25-EU"
          },
          "manufacturer" : {
            "type" : "string",
            "example" : "Quectel"
          },
          "imei" : {
            "type" : "string"
          },
          "iccid" : {
            "type" : "string",
            "description" : "SIM card ICCID"
          },
          "simState" : {
            "type" : "string",
            "description" : "SIM card state (e.g. \"Inserted\")",
            "example" : "Inserted"
          },
          "pinAttemptsLeft" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Remaining PIN attempts before the SIM locks"
          },
          "state" : {
            "type" : "string",
            "description" : "Overall modem state (e.g. \"Connected\")",
            "example" : "Connected"
          },
          "networkState" : {
            "type" : "string",
            "description" : "Mobile network registration state",
            "example" : "Registered, home"
          },
          "operatorName" : {
            "type" : "string",
            "description" : "Registered mobile operator",
            "example" : "TELEKOM.RO"
          },
          "signalQuality" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Signal quality percentage (0-100)"
          },
          "rssi" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Received Signal Strength Indicator in dBm"
          },
          "connectionType" : {
            "type" : "string",
            "description" : "Active radio access technology (e.g. \"2G (EDGE)\", \"4G (LTE)\")",
            "example" : "2G (EDGE)"
          },
          "dataConnectionState" : {
            "type" : "string",
            "description" : "Mobile data connection state (e.g. \"Connected\")"
          },
          "temperatureCelsius" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Modem temperature in degrees Celsius"
          },
          "activeSim" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Currently active SIM slot (1 or 2, for dual-SIM devices)"
          },
          "primary" : {
            "type" : "boolean",
            "description" : "Whether this is the device's primary modem"
          },
          "rxBytes" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Cumulative bytes received on the mobile connection"
          },
          "txBytes" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Cumulative bytes transmitted on the mobile connection"
          }
        }
      },
      "ModemDTO" : {
        "type" : "object",
        "description" : "A TRB140 registered in the gateway (never includes the password)",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "host" : {
            "type" : "string",
            "description" : "REST address of the device",
            "example" : "http://192.168.1.80"
          },
          "username" : {
            "type" : "string"
          },
          "modemRef" : {
            "type" : "string",
            "description" : "The device's modem id used in its REST API",
            "example" : "1-1"
          },
          "phoneNumber" : {
            "type" : "string",
            "description" : "Number of the SIM card (informational)"
          },
          "maxPerMinute" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "SMS per minute this modem/SIM can really send"
          },
          "enabled" : {
            "type" : "boolean"
          },
          "defaultModem" : {
            "type" : "boolean",
            "description" : "Serves users without an assigned modem and machine clients"
          },
          "assignedUsers" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Users assigned to this modem (users without one are not counted, even on the default modem)"
          },
          "circuitState" : {
            "type" : "string",
            "enum" : [ "CLOSED", "HALF_OPEN", "OPEN", "UNKNOWN" ],
            "description" : "Circuit breaker of this modem - OPEN means it was unreachable and its messages wait; UNKNOWN until first used"
          },
          "lastCheckedAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "When the health check last ran for this modem"
          },
          "degraded" : {
            "type" : "boolean",
            "description" : "The last health check failed or found an issue, or the circuit is open"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "CreateModemRequestDTO" : {
        "type" : "object",
        "required" : [ "name", "host", "username", "password", "modemRef" ],
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 100
          },
          "host" : {
            "type" : "string",
            "maxLength" : 255,
            "description" : "http(s) address of the device, without a trailing path",
            "example" : "http://192.168.1.80"
          },
          "username" : {
            "type" : "string",
            "maxLength" : 100
          },
          "password" : {
            "type" : "string",
            "maxLength" : 200,
            "writeOnly" : true
          },
          "modemRef" : {
            "type" : "string",
            "maxLength" : 20,
            "description" : "The device's modem id, e.g. 1-1 (cat /etc/board.json | jsonfilter -e '@.modems[0].id')"
          },
          "phoneNumber" : {
            "type" : "string",
            "maxLength" : 20
          },
          "maxPerMinute" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "maximum" : 600,
            "default" : 20
          },
          "enabled" : {
            "type" : "boolean",
            "default" : true
          }
        }
      },
      "UpdateModemRequestDTO" : {
        "type" : "object",
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 100
          },
          "host" : {
            "type" : "string",
            "maxLength" : 255
          },
          "username" : {
            "type" : "string",
            "maxLength" : 100
          },
          "password" : {
            "type" : "string",
            "maxLength" : 200,
            "writeOnly" : true,
            "description" : "Omit to keep the stored password"
          },
          "modemRef" : {
            "type" : "string",
            "maxLength" : 20
          },
          "phoneNumber" : {
            "type" : "string",
            "maxLength" : 20
          },
          "maxPerMinute" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "maximum" : 600
          },
          "enabled" : {
            "type" : "boolean"
          }
        }
      },
      "AssignModemRequestDTO" : {
        "type" : "object",
        "properties" : {
          "modemId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "The modem to assign - null (or omitted) removes the assignment"
          }
        }
      },
      "ChangePasswordRequestDTO" : {
        "type" : "object",
        "required" : [ "currentPassword", "newPassword" ],
        "properties" : {
          "currentPassword" : {
            "type" : "string"
          },
          "newPassword" : {
            "type" : "string",
            "minLength" : 8,
            "maxLength" : 72,
            "description" : "At least 8 characters, at most 72 (BCrypt limit)"
          }
        }
      },
      "TenantDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "enabled" : {
            "type" : "boolean"
          },
          "quietHoursStart" : {
            "type" : "string"
          },
          "quietHoursEnd" : {
            "type" : "string"
          },
          "timezone" : {
            "type" : "string"
          },
          "retentionDays" : {
            "type" : "integer",
            "format" : "int32"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "CreateTenantRequestDTO" : {
        "type" : "object",
        "required" : [ "name" ],
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 100
          }
        }
      },
      "UpdateTenantRequestDTO" : {
        "type" : "object",
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 100
          },
          "enabled" : {
            "type" : "boolean"
          },
          "quietHoursStart" : {
            "type" : "string",
            "pattern" : "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "description" : "Start of the daily window (HH:mm) in which BULK messages are held back"
          },
          "quietHoursEnd" : {
            "type" : "string",
            "pattern" : "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "description" : "End of the window (HH:mm); may be earlier than the start (overnight)"
          },
          "clearQuietHours" : {
            "type" : "boolean",
            "description" : "Remove the quiet window"
          },
          "retentionDays" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "description" : "Days message bodies are kept before being redacted (default - platform setting)"
          },
          "timezone" : {
            "type" : "string",
            "description" : "IANA zone of the window (default Europe/Bucharest)"
          }
        }
      },
      "CreateApiKeyRequestDTO" : {
        "type" : "object",
        "example" : {
          "name" : "billing-service prod",
          "scopes" : [ "sms:send", "sms:read" ]
        },
        "required" : [ "name" ],
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 100,
            "description" : "Human label (e.g. \"billing-service prod\")"
          },
          "scopes" : {
            "type" : "array",
            "items" : {
              "type" : "string",
              "enum" : [ "sms:send", "sms:read", "contacts:manage", "templates:manage", "webhooks:manage" ]
            },
            "description" : "Omit for all scopes"
          },
          "expiresAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "userId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "ADMIN only — create the key for this user (default - the caller)"
          }
        }
      },
      "ApiKeyDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "tenantId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "userId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "name" : {
            "type" : "string"
          },
          "keyPrefix" : {
            "type" : "string",
            "description" : "First characters of the key — safe to display, identifies the key"
          },
          "scopes" : {
            "type" : "array",
            "items" : {
              "type" : "string"
            }
          },
          "expiresAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "revokedAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "lastUsedAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "ApiKeyCreatedDTO" : {
        "type" : "object",
        "description" : "A newly created key — the only response that ever contains the plaintext secret",
        "properties" : {
          "key" : {
            "type" : "string",
            "description" : "The full API key. Shown once."
          },
          "apiKey" : {
            "$ref" : "#/components/schemas/ApiKeyDTO"
          }
        }
      },
      "SendMessageRequestDTO" : {
        "type" : "object",
        "example" : {
          "to" : "+40760123456",
          "text" : "Comanda 8841 a fost expediata.",
          "priority" : "NORMAL",
          "clientReference" : "order-8841",
          "transliterate" : true
        },
        "required" : [ "to", "text" ],
        "properties" : {
          "to" : {
            "type" : "string",
            "description" : "Recipient in international format (e.g. +40760123456)"
          },
          "text" : {
            "type" : "string",
            "description" : "Message text, max 320 characters. Diacritics switch the SMS to UCS-2 and roughly double its segment count — see `transliterate`."
          },
          "priority" : {
            "type" : "string",
            "enum" : [ "OTP", "NORMAL", "BULK" ],
            "default" : "NORMAL",
            "description" : "OTP is sent before NORMAL, which is sent before BULK"
          },
          "sendAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "Do not send before this time (default - as soon as possible)"
          },
          "validUntil" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "Never send after this time — the message becomes EXPIRED instead (use for OTPs)"
          },
          "clientReference" : {
            "type" : "string",
            "maxLength" : 100,
            "description" : "Your own correlation id, echoed back"
          },
          "transliterate" : {
            "type" : "boolean",
            "default" : false,
            "description" : "Replace diacritics (ă→a, ș→s, ...) to keep the SMS GSM-7 and cheaper"
          }
        }
      },
      "OutboundMessageDTO" : {
        "type" : "object",
        "example" : {
          "id" : "6f1c2b9e-3a5d-4c1e-9b7a-2d4f8e0a1c33",
          "to" : "0040760123456",
          "text" : "Comanda 8841 a fost expediata.",
          "status" : "QUEUED",
          "priority" : "NORMAL",
          "segments" : 1,
          "encoding" : "GSM7",
          "attempts" : 0,
          "clientReference" : "order-8841",
          "estimatedSendAt" : "2026-10-16T09:00:20",
          "createdAt" : "2026-10-16T08:59:58"
        },
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "to" : {
            "type" : "string",
            "description" : "Canonical number, 00-prefixed international format"
          },
          "text" : {
            "type" : "string"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "QUEUED", "SENDING", "SENT", "FAILED", "EXPIRED", "CANCELLED" ]
          },
          "priority" : {
            "type" : "string",
            "enum" : [ "OTP", "NORMAL", "BULK" ]
          },
          "segments" : {
            "type" : "integer",
            "format" : "int32"
          },
          "encoding" : {
            "type" : "string",
            "enum" : [ "GSM7", "UCS2" ]
          },
          "attempts" : {
            "type" : "integer",
            "format" : "int32"
          },
          "errorCode" : {
            "type" : "string",
            "description" : "Stable machine-readable reason when FAILED/EXPIRED/CANCELLED (e.g. GATEWAY_REJECTED, GATEWAY_UNAVAILABLE, UNKNOWN_DELIVERY, EXPIRED)"
          },
          "errorMessage" : {
            "type" : "string"
          },
          "smsUsed" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "SMS parts the modem reports consuming"
          },
          "clientReference" : {
            "type" : "string"
          },
          "sendAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "Earliest/next send attempt time"
          },
          "validUntil" : {
            "type" : "string",
            "format" : "date-time"
          },
          "estimatedSendAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "Rough estimate, present while QUEUED"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "sentAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "OutboundMessagePageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/OutboundMessageDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Pass as `cursor` for the next page; absent on the last page"
          }
        }
      },
      "InboundMessageDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "sender" : {
            "type" : "string"
          },
          "message" : {
            "type" : "string"
          },
          "receivedTime" : {
            "type" : "string",
            "format" : "date-time"
          },
          "countryCode" : {
            "type" : "string"
          },
          "contactId" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "InboundMessagePageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/InboundMessageDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Pass as `cursor` for the next page; absent on the last page"
          }
        }
      },
      "BatchMessageDTO" : {
        "type" : "object",
        "required" : [ "to" ],
        "properties" : {
          "to" : {
            "type" : "string"
          },
          "text" : {
            "type" : "string",
            "description" : "Literal text - mutually exclusive with templateId"
          },
          "templateId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "templateParams" : {
            "type" : "object",
            "additionalProperties" : {
              "type" : "string"
            }
          },
          "priority" : {
            "type" : "string",
            "enum" : [ "OTP", "NORMAL", "BULK" ],
            "description" : "Overrides the batch default"
          },
          "clientReference" : {
            "type" : "string",
            "maxLength" : 100
          }
        }
      },
      "SendBatchRequestDTO" : {
        "type" : "object",
        "example" : {
          "priority" : "BULK",
          "messages" : [ {
            "to" : "+40760111111",
            "text" : "Salut Ana, oferta ta expira azi."
          }, {
            "to" : "+40760222222",
            "templateId" : "0b8e3c7a-6f4d-7a2e-9c1b-4d5e6f7a8b9c",
            "templateParams" : {
              "name" : "Ion"
            }
          } ]
        },
        "required" : [ "messages" ],
        "properties" : {
          "messages" : {
            "type" : "array",
            "minItems" : 1,
            "maxItems" : 1000,
            "items" : {
              "$ref" : "#/components/schemas/BatchMessageDTO"
            }
          },
          "priority" : {
            "type" : "string",
            "enum" : [ "OTP", "NORMAL", "BULK" ],
            "default" : "BULK",
            "description" : "Default priority for items that do not set their own"
          },
          "transliterate" : {
            "type" : "boolean",
            "default" : false
          },
          "sendAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "validUntil" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "BatchItemResultDTO" : {
        "type" : "object",
        "properties" : {
          "index" : {
            "type" : "integer",
            "format" : "int32"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "ACCEPTED", "REJECTED" ]
          },
          "id" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "Present when ACCEPTED"
          },
          "errorCode" : {
            "type" : "string",
            "description" : "Present when REJECTED (INVALID_REQUEST, SUPPRESSED, PLAN_REQUIRED, PLAN_LIMIT_REACHED, QUEUE_CAPACITY, TEMPLATE_NOT_FOUND, IDEMPOTENCY_CONFLICT)"
          },
          "error" : {
            "type" : "string"
          }
        }
      },
      "BatchResponseDTO" : {
        "type" : "object",
        "properties" : {
          "accepted" : {
            "type" : "integer",
            "format" : "int32"
          },
          "rejected" : {
            "type" : "integer",
            "format" : "int32"
          },
          "results" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/BatchItemResultDTO"
            }
          }
        }
      },
      "EstimateRequestDTO" : {
        "type" : "object",
        "required" : [ "to", "text" ],
        "properties" : {
          "to" : {
            "type" : "string"
          },
          "text" : {
            "type" : "string"
          },
          "transliterate" : {
            "type" : "boolean",
            "default" : false
          }
        }
      },
      "EstimateResponseDTO" : {
        "type" : "object",
        "properties" : {
          "to" : {
            "type" : "string"
          },
          "text" : {
            "type" : "string",
            "description" : "The text as it would be sent (after transliteration, if requested)"
          },
          "encoding" : {
            "type" : "string",
            "enum" : [ "GSM7", "UCS2" ]
          },
          "units" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Length in encoding units (septets for GSM7, UTF-16 code units for UCS2)"
          },
          "segments" : {
            "type" : "integer",
            "format" : "int32"
          },
          "countryCode" : {
            "type" : "string"
          },
          "planActive" : {
            "type" : "boolean",
            "description" : "Whether you have an active plan. Present only when plans are enforced on this installation"
          },
          "unlimited" : {
            "type" : "boolean",
            "description" : "Your plan has no SMS limit"
          },
          "unitsAvailable" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS you can still queue (meaningless when unlimited)"
          },
          "sufficient" : {
            "type" : "boolean",
            "description" : "Whether your plan covers this message (`segments` SMS)"
          }
        }
      },
      "CreateWebhookRequestDTO" : {
        "type" : "object",
        "example" : {
          "url" : "https://example.com/hooks/sms",
          "events" : [ "message.sent", "message.failed", "inbound.received" ]
        },
        "required" : [ "url" ],
        "properties" : {
          "url" : {
            "type" : "string",
            "maxLength" : 500
          },
          "events" : {
            "type" : "array",
            "items" : {
              "type" : "string",
              "enum" : [ "message.queued", "message.sent", "message.failed", "message.expired", "message.cancelled", "inbound.received", "plan.credits_low" ]
            },
            "description" : "Omit to subscribe to everything"
          }
        }
      },
      "UpdateWebhookRequestDTO" : {
        "type" : "object",
        "properties" : {
          "url" : {
            "type" : "string",
            "maxLength" : 500
          },
          "events" : {
            "type" : "array",
            "items" : {
              "type" : "string"
            }
          },
          "enabled" : {
            "type" : "boolean"
          }
        }
      },
      "WebhookDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "url" : {
            "type" : "string"
          },
          "events" : {
            "type" : "array",
            "items" : {
              "type" : "string"
            }
          },
          "enabled" : {
            "type" : "boolean"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "WebhookCreatedDTO" : {
        "type" : "object",
        "properties" : {
          "secret" : {
            "type" : "string",
            "description" : "Signing secret - shown once"
          },
          "webhook" : {
            "$ref" : "#/components/schemas/WebhookDTO"
          }
        }
      },
      "WebhookDeliveryDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "webhookId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "eventType" : {
            "type" : "string"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING", "DELIVERED", "FAILED" ]
          },
          "attempts" : {
            "type" : "integer",
            "format" : "int32"
          },
          "responseStatus" : {
            "type" : "integer",
            "format" : "int32"
          },
          "lastError" : {
            "type" : "string"
          },
          "nextAttemptAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "deliveredAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "WebhookDeliveryPageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/WebhookDeliveryDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "SuppressionDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "number" : {
            "type" : "string"
          },
          "reason" : {
            "type" : "string",
            "enum" : [ "STOP_KEYWORD", "MANUAL" ]
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "SuppressionPageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/SuppressionDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "AddSuppressionRequestDTO" : {
        "type" : "object",
        "required" : [ "number" ],
        "properties" : {
          "number" : {
            "type" : "string"
          }
        }
      },
      "StartVerificationRequestDTO" : {
        "type" : "object",
        "example" : {
          "to" : "+40760123456",
          "codeLength" : 6,
          "ttlSeconds" : 300
        },
        "required" : [ "to" ],
        "properties" : {
          "to" : {
            "type" : "string"
          },
          "codeLength" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 6,
            "minimum" : 4,
            "maximum" : 8
          },
          "ttlSeconds" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 300,
            "minimum" : 60,
            "maximum" : 900
          },
          "template" : {
            "type" : "string",
            "description" : "Custom message; must contain {{code}} ({{minutes}} is also available)"
          }
        }
      },
      "VerificationDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING", "VERIFIED", "FAILED", "EXPIRED" ]
          },
          "messageId" : {
            "type" : "string",
            "format" : "uuid",
            "description" : "The queued SMS carrying the code - poll GET /api/v1/messages/{id} for delivery"
          },
          "expiresAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "CheckVerificationRequestDTO" : {
        "type" : "object",
        "required" : [ "id", "code" ],
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "code" : {
            "type" : "string"
          }
        }
      },
      "CheckVerificationResponseDTO" : {
        "type" : "object",
        "properties" : {
          "valid" : {
            "type" : "boolean"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING", "VERIFIED", "FAILED", "EXPIRED" ]
          },
          "attemptsLeft" : {
            "type" : "integer",
            "format" : "int32"
          }
        }
      },
      "AuditLogEntryDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "at" : {
            "type" : "string",
            "format" : "date-time"
          },
          "actor" : {
            "type" : "string"
          },
          "tenantId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "action" : {
            "type" : "string"
          },
          "target" : {
            "type" : "string"
          },
          "details" : {
            "type" : "string"
          }
        }
      },
      "AuditLogPageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/AuditLogEntryDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "PlanDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "code" : {
            "type" : "string"
          },
          "name" : {
            "type" : "string"
          },
          "description" : {
            "type" : "string"
          },
          "type" : {
            "type" : "string",
            "enum" : [ "PACKAGE", "PER_SMS", "UNLIMITED" ]
          },
          "smsQuantity" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "PACKAGE - how many SMS it contains"
          },
          "priceNet" : {
            "type" : "number",
            "description" : "PACKAGE - total price, excluding VAT"
          },
          "unitPrice" : {
            "type" : "number",
            "description" : "PER_SMS - price of one SMS, excluding VAT"
          },
          "minQuantity" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "PER_SMS - smallest quantity that can be assigned"
          },
          "rateLimitPerMinute" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "API requests per minute on this plan; absent = platform default"
          },
          "adminOnly" : {
            "type" : "boolean"
          },
          "active" : {
            "type" : "boolean"
          },
          "sortOrder" : {
            "type" : "integer",
            "format" : "int32"
          },
          "updatedAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "PlanRequestDTO" : {
        "type" : "object",
        "required" : [ "name", "type" ],
        "example" : {
          "code" : "SMALL",
          "name" : "Small",
          "description" : "100 SMS",
          "type" : "PACKAGE",
          "smsQuantity" : 100,
          "priceNet" : 21
        },
        "properties" : {
          "code" : {
            "type" : "string",
            "minLength" : 2,
            "maxLength" : 40,
            "pattern" : "^[A-Za-z0-9_\\-]+$",
            "description" : "Required when creating; ignored when updating (a code never changes)"
          },
          "name" : {
            "type" : "string",
            "minLength" : 1,
            "maxLength" : 100
          },
          "description" : {
            "type" : "string",
            "maxLength" : 300
          },
          "type" : {
            "type" : "string",
            "enum" : [ "PACKAGE", "PER_SMS", "UNLIMITED" ]
          },
          "smsQuantity" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "description" : "PACKAGE only"
          },
          "priceNet" : {
            "type" : "number",
            "minimum" : 0,
            "description" : "PACKAGE (and optionally UNLIMITED) - total price, excluding VAT"
          },
          "unitPrice" : {
            "type" : "number",
            "description" : "PER_SMS only - price of one SMS, excluding VAT"
          },
          "minQuantity" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "description" : "PER_SMS only, default 1"
          },
          "rateLimitPerMinute" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1
          },
          "adminOnly" : {
            "type" : "boolean",
            "default" : false
          },
          "active" : {
            "type" : "boolean",
            "default" : true
          },
          "sortOrder" : {
            "type" : "integer",
            "format" : "int32",
            "default" : 0
          }
        }
      },
      "AssignPlanRequestDTO" : {
        "type" : "object",
        "required" : [ "planId" ],
        "example" : {
          "planId" : "0b8e3c7a-6f4d-7a2e-9c1b-4d5e6f7a8b9c"
        },
        "properties" : {
          "planId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "quantity" : {
            "type" : "integer",
            "format" : "int32",
            "minimum" : 1,
            "description" : "Number of SMS - required for PER_SMS plans, not allowed for others"
          },
          "note" : {
            "type" : "string",
            "maxLength" : 300
          }
        }
      },
      "UserPlanDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "userId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "planId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "PENDING_PAYMENT", "ACTIVE", "SUPERSEDED", "CANCELLED" ]
          },
          "planCode" : {
            "type" : "string"
          },
          "planName" : {
            "type" : "string"
          },
          "planType" : {
            "type" : "string",
            "enum" : [ "PACKAGE", "PER_SMS", "UNLIMITED" ]
          },
          "smsQuantity" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "SMS credited when the plan is activated; absent for UNLIMITED"
          },
          "unitPrice" : {
            "type" : "number"
          },
          "priceNet" : {
            "type" : "number",
            "description" : "Total price excluding VAT, as it was when assigned"
          },
          "rateLimitPerMinute" : {
            "type" : "integer",
            "format" : "int32"
          },
          "assignedBy" : {
            "type" : "string",
            "format" : "uuid"
          },
          "note" : {
            "type" : "string"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "activatedAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "BillingProfileDTO" : {
        "type" : "object",
        "properties" : {
          "name" : {
            "type" : "string",
            "maxLength" : 150
          },
          "cui" : {
            "type" : "string",
            "maxLength" : 30
          },
          "address" : {
            "type" : "string",
            "maxLength" : 300
          },
          "email" : {
            "type" : "string",
            "maxLength" : 150
          }
        }
      },
      "BillingSummaryDTO" : {
        "type" : "object",
        "properties" : {
          "userId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "activePlan" : {
            "$ref" : "#/components/schemas/UserPlanDTO"
          },
          "pendingPlan" : {
            "$ref" : "#/components/schemas/UserPlanDTO"
          },
          "unlimited" : {
            "type" : "boolean"
          },
          "unitsAvailable" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS you can still queue (meaningless when unlimited)"
          },
          "unitsReserved" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS held for messages still waiting in the queue"
          },
          "unitsUsedTotal" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS sent since the beginning"
          },
          "amountDue" : {
            "type" : "number",
            "description" : "Gross total of your unpaid invoices"
          },
          "currency" : {
            "type" : "string"
          },
          "billingProfile" : {
            "$ref" : "#/components/schemas/BillingProfileDTO"
          },
          "history" : {
            "type" : "array",
            "description" : "Every assignment, newest first - only in the administrator view",
            "items" : {
              "$ref" : "#/components/schemas/UserPlanDTO"
            }
          }
        }
      },
      "InvoiceItemDTO" : {
        "type" : "object",
        "properties" : {
          "position" : {
            "type" : "integer",
            "format" : "int32"
          },
          "description" : {
            "type" : "string"
          },
          "quantity" : {
            "type" : "integer",
            "format" : "int32"
          },
          "unitPrice" : {
            "type" : "number"
          },
          "amountNet" : {
            "type" : "number"
          },
          "vatRate" : {
            "type" : "number"
          },
          "vatAmount" : {
            "type" : "number"
          },
          "amountGross" : {
            "type" : "number"
          }
        }
      },
      "InvoiceDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "number" : {
            "type" : "string"
          },
          "userId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "userPlanId" : {
            "type" : "string",
            "format" : "uuid"
          },
          "status" : {
            "type" : "string",
            "enum" : [ "UNPAID", "PAID", "CANCELLED" ]
          },
          "currency" : {
            "type" : "string"
          },
          "amountNet" : {
            "type" : "number"
          },
          "vatRate" : {
            "type" : "number"
          },
          "vatAmount" : {
            "type" : "number"
          },
          "amountGross" : {
            "type" : "number",
            "description" : "What has to be paid"
          },
          "customer" : {
            "$ref" : "#/components/schemas/BillingProfileDTO"
          },
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/InvoiceItemDTO"
            }
          },
          "issuedAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "paidAt" : {
            "type" : "string",
            "format" : "date-time"
          },
          "paymentReference" : {
            "type" : "string"
          },
          "cancelledAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "InvoicePageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/InvoiceDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "PlanAssignmentDTO" : {
        "type" : "object",
        "properties" : {
          "userPlan" : {
            "$ref" : "#/components/schemas/UserPlanDTO"
          },
          "invoice" : {
            "$ref" : "#/components/schemas/InvoiceDTO"
          }
        }
      },
      "MarkInvoicePaidRequestDTO" : {
        "type" : "object",
        "required" : [ "reference" ],
        "example" : {
          "reference" : "OP 2026-10-16 / bank transfer"
        },
        "properties" : {
          "reference" : {
            "type" : "string",
            "minLength" : 1,
            "maxLength" : 100,
            "description" : "Payment reference (bank transfer, receipt number, ...)"
          },
          "paidAt" : {
            "type" : "string",
            "format" : "date-time",
            "description" : "When the payment was received; defaults to now"
          }
        }
      },
      "UsageAdjustmentRequestDTO" : {
        "type" : "object",
        "required" : [ "units", "reason" ],
        "example" : {
          "units" : -5,
          "reason" : "Goodwill correction"
        },
        "properties" : {
          "units" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "SMS to add (positive) or remove (negative); never below zero"
          },
          "reason" : {
            "type" : "string",
            "minLength" : 1,
            "maxLength" : 300
          }
        }
      },
      "UsageEntryDTO" : {
        "type" : "object",
        "properties" : {
          "id" : {
            "type" : "string",
            "format" : "uuid"
          },
          "type" : {
            "type" : "string",
            "enum" : [ "CREDIT", "RESERVE", "CAPTURE", "RELEASE", "ADJUSTMENT" ]
          },
          "units" : {
            "type" : "integer",
            "format" : "int32",
            "description" : "Positive, except for ADJUSTMENT (signed)"
          },
          "availableAfter" : {
            "type" : "integer",
            "format" : "int32"
          },
          "reservedAfter" : {
            "type" : "integer",
            "format" : "int32"
          },
          "reference" : {
            "type" : "string",
            "description" : "The message id for RESERVE / CAPTURE / RELEASE"
          },
          "description" : {
            "type" : "string"
          },
          "createdAt" : {
            "type" : "string",
            "format" : "date-time"
          }
        }
      },
      "UsageEntryPageDTO" : {
        "type" : "object",
        "properties" : {
          "items" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/UsageEntryDTO"
            }
          },
          "nextCursor" : {
            "type" : "string",
            "format" : "uuid"
          }
        }
      },
      "UsageRowDTO" : {
        "type" : "object",
        "properties" : {
          "period" : {
            "type" : "string",
            "format" : "date-time"
          },
          "sent" : {
            "type" : "integer",
            "format" : "int64"
          },
          "failed" : {
            "type" : "integer",
            "format" : "int64"
          },
          "expired" : {
            "type" : "integer",
            "format" : "int64"
          },
          "cancelled" : {
            "type" : "integer",
            "format" : "int64"
          },
          "segments" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "Segments of SENT messages"
          },
          "chargedUnits" : {
            "type" : "integer",
            "format" : "int64",
            "description" : "SMS counted against plans for SENT messages"
          }
        }
      },
      "UsageDTO" : {
        "type" : "object",
        "properties" : {
          "from" : {
            "type" : "string",
            "format" : "date-time"
          },
          "to" : {
            "type" : "string",
            "format" : "date-time"
          },
          "granularity" : {
            "type" : "string"
          },
          "totals" : {
            "$ref" : "#/components/schemas/UsageRowDTO"
          },
          "rows" : {
            "type" : "array",
            "items" : {
              "$ref" : "#/components/schemas/UsageRowDTO"
            }
          }
        }
      },
      "ProblemDetailDTO" : {
        "type" : "object",
        "description" : "RFC 7807 error body (`application/problem+json`). Quote `requestId` when contacting support.",
        "properties" : {
          "type" : {
            "type" : "string",
            "example" : "about:blank"
          },
          "title" : {
            "type" : "string",
            "example" : "Bad Request"
          },
          "status" : {
            "type" : "integer",
            "format" : "int32",
            "example" : 400
          },
          "detail" : {
            "type" : "string",
            "example" : "Invalid phone number - provide it in international format (e.g. +40760123456)"
          },
          "instance" : {
            "type" : "string"
          },
          "requestId" : {
            "type" : "string",
            "description" : "Also returned in the X-Request-ID header"
          },
          "errorCode" : {
            "type" : "string",
            "description" : "Stable machine-readable code where one applies (SUPPRESSED, PLAN_REQUIRED, PLAN_LIMIT_REACHED, QUEUE_CAPACITY)",
            "example" : "SUPPRESSED"
          }
        }
      }
    }
  }
}