openapi: 3.0.3 info: title: MAAC Go Broadcast SMS API version: 1.0.0 description: "MAAC Go — Taiwan-first self-serve SMS platform. Send transactional (OTP /\norder / reminder) and broadcast SMS with NCC compliance, MAAC delivery\nbackend, and real-time DLR webhooks.\n\n- Base URL: `https://sms.cresclab.com/api`\n- Auth: `Authorization: Bearer `\n- New accounts receive NT$50 trial credit on signup, enough to send real test SMS immediately.\n- API keys are scoped and wallet-backed; sending debits the account balance unless your deployment uses a mock SMS/payment adapter.\n- All phone numbers use E.164 (`+886912345678`). 09xxxxxxxx is also accepted.\n- Cost: NT$0.78 per SMS segment (Chinese: 70 chars/segment · English: 160/segment).\n- Rate-limit errors return `429 rate_limited` with limit/used details and retry guidance when available.\n- Delivery confirmations arrive asynchronously via webhook (`sms.delivered`\n / `sms.failed`); poll `GET /sms/{id}` as a fallback.\n- MCP clients (Claude / Cursor / Windsurf) can use this spec directly, or\n connect via `POST /api/mcp` (JSON-RPC 2.0) with the same bearer key.\n" contact: name: MAAC Go Support email: info@cresclab.com url: https://sms.cresclab.com license: name: Commercial url: https://sms.cresclab.com/terms.html servers: - url: https://sms.cresclab.com/api description: Production security: - bearerAuth: [] tags: - name: SMS description: Transactional (1-to-1) SMS send + status. paths: /sms/send: post: tags: - SMS summary: Send a transactional SMS (1-to-1) description: 'OTP, order notifications, appointment reminders. High-priority channel, no LINE fallback. Returns after MAAC accepts the request; final delivery status arrives via the `sms.delivered` / `sms.failed` webhook. ' operationId: sendSms requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SmsSendRequest' examples: otp: value: to: '+886912345678' body: 【YourBrand】驗證碼 483291,5 分鐘內有效。勿告知他人。 type: otp notification: value: to: '+886912345678' body: 【YourBrand】訂單 A12345 已出貨,預計 3/15 送達。 type: notification responses: '200': description: Queued for delivery content: application/json: schema: $ref: '#/components/schemas/SmsSendResponse' '400': description: Validation or NCC compliance failure content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: ncc: value: error: ncc_blocked issues: - level: block code: SHORTENER reason: bit.ly is on NCC 2024/11 blocklist '401': description: Auth missing / invalid content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '402': description: Wallet balance insufficient content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '429': description: Rate limit exceeded content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /sms/{id}: get: tags: - SMS summary: Retrieve SMS status description: Poll once per 5+ seconds as a fallback; prefer the `sms.delivered` webhook. operationId: getSms parameters: - in: path name: id required: true schema: type: string example: sms_abc123 responses: '200': description: Current message state content: application/json: schema: $ref: '#/components/schemas/SmsDetail' '404': description: Not found /sms/list: get: tags: - SMS summary: List recent messages (Resend-style delivery log) operationId: listSms parameters: - in: query name: limit schema: type: integer minimum: 1 maximum: 200 default: 50 - in: query name: status schema: type: string enum: - queued - sent - delivered - failed - stop - in: query name: team description: Filter to one cost-attribution team. Use the literal `(未分類)` for untagged messages. schema: type: string example: 付費會員 responses: '200': description: OK content: application/json: schema: type: object properties: ok: type: boolean messages: type: array items: $ref: '#/components/schemas/SmsDetail' summary_7d: type: object additionalProperties: type: integer /sms/by-team: get: tags: - SMS summary: Cost attribution per team (messages / cost / delivered / failed) description: 'Returns one row per team plus `(未分類)` for untagged sends, so the rows sum to the account total. Money is shared (one wallet, one invoice per 統編); this is a reporting breakdown for internal chargeback, **not** a wallet split. Windowing: pass `from`/`to` for an exact billing period (monthly chargeback), or `days` for a rolling window. `from`/`to` take precedence when supplied. ' operationId: smsByTeam parameters: - in: query name: days description: Rolling window in days (ignored if from/to given). schema: type: integer default: 30 maximum: 90 - in: query name: from description: Start of an exact range (ISO date/datetime, inclusive). schema: type: string format: date-time example: '2026-06-01' - in: query name: to description: End of an exact range (ISO date/datetime, exclusive). schema: type: string format: date-time example: '2026-07-01' responses: '200': description: OK content: application/json: schema: type: object properties: ok: type: boolean days: type: integer description: Present in rolling-window mode from: type: string nullable: true description: Present in range mode to: type: string nullable: true description: Present in range mode total_cost_cents: type: integer teams: type: array items: type: object properties: team: type: string description: Team name, or (未分類) for untagged msgs: type: integer cost_cents: type: integer delivered: type: integer failed: type: integer /sms/metrics: get: tags: - SMS summary: Daily volume + delivery metrics operationId: getMetrics parameters: - in: query name: days schema: type: integer default: 30 maximum: 90 responses: '200': description: Aggregated metrics content: application/json: schema: $ref: '#/components/schemas/MetricsResponse' components: schemas: SmsDetail: type: object properties: id: type: string to: type: string body: type: string status: type: string enum: - queued - sent - delivered - failed - stop segments: type: integer cost_cents: type: integer sent_at: type: string format: date-time nullable: true delivered_at: type: string format: date-time nullable: true error: type: string nullable: true gateway_ref: type: string nullable: true description: Upstream MAAC pnp_message_id SmsSendResponse: type: object properties: ok: type: boolean message_id: type: string example: sms_abc123 status: type: string enum: - queued - sent - delivered - failed segments: type: integer cost_cents: type: integer description: Amount debited in NT cents (78 = NT$0.78) balance_cents: type: integer team: type: string nullable: true description: Resolved cost-attribution team (null = 未分類). Echo of what was tagged. team_created: type: boolean description: True if this send auto-created the team (first use of the name). ErrorResponse: type: object properties: error: type: string example: ncc_blocked hint: type: string issues: type: array items: type: object properties: level: type: string enum: - block - warn code: type: string reason: type: string SmsSendRequest: type: object required: - to - body properties: to: type: string description: E.164 (`+886912345678`) or TW local (`0912345678`) example: '+886912345678' body: type: string maxLength: 1000 description: SMS text. 70 chars = 1 segment (Chinese), 160 (ASCII). Must include `【brand】` prefix and `STOP` / `退訂` for marketing. from: type: string description: Sender ID. Defaults to Cresclab shared short-code `1990`. type: type: string enum: - otp - notification - marketing description: Controls routing priority and compliance checks. team: type: string description: Cost-attribution team for this send (e.g. `付費會員`). Overrides the API key's bound team. An unknown name is auto-created, so a typo surfaces as a stray team rather than vanishing. Omit to inherit the key's team or fall to 未分類. example: 付費會員 MetricsResponse: type: object properties: ok: type: boolean days: type: integer daily: type: array items: type: object properties: day: type: string format: date total: type: integer delivered: type: integer failed: type: integer cost_cents: type: integer totals: type: object properties: total: type: integer delivered: type: integer failed: type: integer cost_cents: type: integer avg_latency_sec: type: number nullable: true securitySchemes: bearerAuth: type: http scheme: bearer description: '`Authorization: Bearer `. Production keys start with `sk_live_`, test keys with `sk_test_` for environment separation. Sending still follows the account wallet and deployment adapter settings. '