Initial commit
This commit is contained in:
245
API.md
Normal file
245
API.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# SMS Gateway API
|
||||
|
||||
All endpoints are JSON over HTTP.
|
||||
|
||||
## Base URL
|
||||
|
||||
- Local: `http://localhost:8080`
|
||||
|
||||
## Authentication (Send SMS)
|
||||
|
||||
`POST /api/sms/send` requires:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: Bearer <API Key>`
|
||||
|
||||
The API key is generated when creating an application and stored in `applications.api_key`.
|
||||
|
||||
## Pagination
|
||||
|
||||
For list endpoints:
|
||||
|
||||
- `page` (default `0`)
|
||||
- `size` (default `20`, max `200`)
|
||||
- `sort` (default `createdAt,desc`) format: `field,asc|desc`
|
||||
|
||||
Spring returns a `Page<T>` JSON with fields like `content`, `totalElements`, `totalPages`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Clients
|
||||
|
||||
### Create client
|
||||
|
||||
`POST /api/clients`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Acme Corp",
|
||||
"email": "contact@acme.com",
|
||||
"country": "Kenya",
|
||||
"phoneNumber": "+254700000000"
|
||||
}
|
||||
```
|
||||
|
||||
Response `201`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Acme Corp",
|
||||
"email": "contact@acme.com",
|
||||
"country": "Kenya",
|
||||
"phoneNumber": "+254700000000",
|
||||
"status": "NEW",
|
||||
"createdAt": "2026-03-17T06:00:00Z",
|
||||
"updatedAt": "2026-03-17T06:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### List clients (paginated)
|
||||
|
||||
`GET /api/clients?page=0&size=20&sort=createdAt,desc`
|
||||
|
||||
### Get client by id
|
||||
|
||||
`GET /api/clients/{id}`
|
||||
|
||||
### Update client status (NEW -> ACTIVE/INACTIVE)
|
||||
|
||||
`PATCH /api/clients/{id}/status`
|
||||
|
||||
Headers:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ACTIVE"
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Only accepts `ACTIVE` or `INACTIVE`.
|
||||
|
||||
---
|
||||
|
||||
## Applications
|
||||
|
||||
### Create application (generates API key)
|
||||
|
||||
`POST /api/applications`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My SMS App",
|
||||
"type": "LOCAL",
|
||||
"orgId": 1
|
||||
}
|
||||
```
|
||||
|
||||
Response `201` (includes `apiKey`):
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "My SMS App",
|
||||
"type": "LOCAL",
|
||||
"orgId": 1,
|
||||
"apiKey": "sms_....",
|
||||
"createdAt": "2026-03-17T06:00:00Z",
|
||||
"updatedAt": "2026-03-17T06:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### List applications (paginated)
|
||||
|
||||
`GET /api/applications?page=0&size=20&sort=createdAt,desc`
|
||||
|
||||
Note: list responses do **not** include `apiKey`.
|
||||
|
||||
### List applications by client id (paginated)
|
||||
|
||||
`GET /api/applications/client/{clientId}?page=0&size=20&sort=createdAt,desc`
|
||||
|
||||
### Get application by id
|
||||
|
||||
`GET /api/applications/{id}`
|
||||
|
||||
---
|
||||
|
||||
## Send SMS
|
||||
|
||||
### Send SMS (queue message)
|
||||
|
||||
`POST /api/sms/send`
|
||||
|
||||
Headers:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
- `Authorization: Bearer <API Key>`
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"from": "SENDER_ID",
|
||||
"to": "2547XXXXXXXX",
|
||||
"refId": "client-app-ref-12345",
|
||||
"message": "Hello"
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `refId` is the **client reference id** stored in `messages.client_reference_id`.
|
||||
- The backend generates `messageRefId` (UUID) stored in `messages.internal_reference_id`.
|
||||
- When sending to UCM, we submit `refId = messages.internal_reference_id` (our UUID) for end-to-end tracking.
|
||||
- The poller later sends NEW messages to UCM and updates `messages.ucm_message_id` using UCM `msgId`.
|
||||
|
||||
Response `201`:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "NEW",
|
||||
"to": "2547XXXXXXXX",
|
||||
"messageRefId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||
"created_at": "2026-03-17 09:33:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UCM Callbacks
|
||||
|
||||
### Delivery receipts callback (configured on UCM)
|
||||
|
||||
`POST /api/ucm/delivery-receipts`
|
||||
|
||||
Headers:
|
||||
|
||||
- `Content-Type: application/json`
|
||||
|
||||
Payload:
|
||||
|
||||
```json
|
||||
{
|
||||
"msgId": "tr-29dd2622-c1c4-46f1-93cb-e40452471924",
|
||||
"from": "2547XXXXXXXX",
|
||||
"to": "TIARA",
|
||||
"refId": "TIARA",
|
||||
"status": "DeliveredToTerminal",
|
||||
"statusReason": "DeliveredToTerminal",
|
||||
"deliveryTime": "2019-02-05 18:27:39.878"
|
||||
}
|
||||
```
|
||||
|
||||
How it updates `messages`:
|
||||
|
||||
- Looks up by: `messages.ucm_message_id == msgId`
|
||||
- Updates:
|
||||
- `messages.ucm_delivery_status = status`
|
||||
- `messages.delivery_time = deliveryTime`
|
||||
- If `status == DeliveredToTerminal`: `messages.delivery_status = DELIVERED`
|
||||
|
||||
---
|
||||
|
||||
## Messages
|
||||
|
||||
### List messages (paginated)
|
||||
|
||||
`GET /api/messages?page=0&size=20&sort=createdAt,desc`
|
||||
|
||||
### List messages by client id (paginated)
|
||||
|
||||
`GET /api/messages/client/{clientId}?page=0&size=20&sort=createdAt,desc`
|
||||
|
||||
---
|
||||
|
||||
## Error format
|
||||
|
||||
### Validation errors (400)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "about:blank",
|
||||
"title": "Validation Failed",
|
||||
"status": 400,
|
||||
"detail": "Request validation failed",
|
||||
"errors": [
|
||||
{ "field": "to", "message": "Recipient (to) is required" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Auth errors (401)
|
||||
|
||||
Missing or invalid `Authorization: Bearer <API Key>` returns a `ProblemDetail` response with `status=401`.
|
||||
|
||||
Reference in New Issue
Block a user