ChatmaidDevelopers

Cookbook

End-to-end implementation workflow from setup to production.

Architecture

This cookbook covers the full runtime loop for an integration service using API-key auth only:

  1. Load API key and set a single base URL.
  2. Fetch connected sender phone numbers.
  3. Send outbound message requests.
  4. Track status via polling and optional webhooks.
  5. Apply retry/idempotency strategy and promote to production.

Integration host: https://developers-api.chatmaid.net.

Bootstrap Client

Standardize your HTTP client once and reuse it for all endpoints.

export CHATMAID_BASE_URL="https://developers-api.chatmaid.net"
export CHATMAID_API_KEY="sk_test_xxx"

curl -X GET "$CHATMAID_BASE_URL/v1/account" \
  -H "Authorization: Bearer $CHATMAID_API_KEY"
const chatmaid = {
  baseUrl: process.env.CHATMAID_BASE_URL || "https://developers-api.chatmaid.net",
  apiKey: process.env.CHATMAID_API_KEY!,
  async request(path: string, init: RequestInit = {}) {
    const response = await fetch(this.baseUrl + path, {
      ...init,
      headers: {
        Authorization: "Bearer " + this.apiKey,
        "Content-Type": "application/json",
        ...(init.headers || {}),
      },
    });
    return response.json();
  },
};
import os
import requests

class ChatmaidClient:
    def __init__(self):
        self.base_url = os.getenv("CHATMAID_BASE_URL", "https://developers-api.chatmaid.net")
        self.api_key = os.environ["CHATMAID_API_KEY"]

    def request(self, method: str, path: str, **kwargs):
        headers = kwargs.pop("headers", {})
        headers.update({
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json",
        })
        response = requests.request(method, self.base_url + path, headers=headers, **kwargs)
        return response.json()

Send and Track

Use this operational sequence inside your application flow.

1) Resolve Sender Phone ID

GET /v1/phone-numbers

Read available phone numbers and select a connected sender.

curl -X GET https://developers-api.chatmaid.net/v1/phone-numbers \
  -H "Authorization: Bearer sk_test_xxx"
const phones = await chatmaid.request("/v1/phone-numbers");
const connected = phones.data.find((phone: any) => phone.connectionStatus === "connected");
phones = client.request("GET", "/v1/phone-numbers")
connected = next((p for p in phones["data"] if p["connectionStatus"] == "connected"), None)

2) Send Message

POST /v1/messages/send

Create outbound message. Use optional idempotency key only for retry-heavy workflows.

curl -X POST https://developers-api.chatmaid.net/v1/messages/send \
  -H "Authorization: Bearer sk_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "fromPhoneId": "+15551234567",
    "to": "+15557654321",
    "content": "Order 7784 confirmed"
  }'
const sent = await chatmaid.request("/v1/messages/send", {
  method: "POST",
  body: JSON.stringify({
    fromPhoneId: "+15551234567",
    to: "+15557654321",
    content: "Order 7784 confirmed",
  }),
});

const messageId = sent.data.id;
sent = client.request(
    "POST",
    "/v1/messages/send",
    json={
        "fromPhoneId": "+15551234567",
        "to": "+15557654321",
        "content": "Order 7784 confirmed",
    },
)
message_id = sent["data"]["id"]
{
  "success": true,
  "data": {
    "id": "msg_abc123def456",
    "status": "sent",
    "createdAt": "2026-02-06T14:18:33.000Z"
  }
}

3) Poll Status Until Terminal

GET /v1/messages/:messageId

Poll until status reaches sent or failed.

curl -X GET https://developers-api.chatmaid.net/v1/messages/msg_abc123def456 \
  -H "Authorization: Bearer sk_test_xxx"
async function waitForTerminal(messageId: string) {
  while (true) {
    const current = await chatmaid.request("/v1/messages/" + messageId);
    const status = current.data.status;
    if (["sent", "failed"].includes(status)) return current;
    await new Promise((resolve) => setTimeout(resolve, 1500));
  }
}
import time

def wait_for_terminal(message_id: str):
    while True:
        current = client.request("GET", f"/v1/messages/{message_id}")
        status = current["data"]["status"]
        if status in ["sent", "failed"]:
            return current
        time.sleep(1.5)

Reliability Patterns

  • Treat messageId as your canonical correlation key across systems.
  • Use webhook events for push updates; keep polling as fallback.
  • Use idempotencyKey only when your retry policy can repeat sends.
  • Capture and alert on failed statuses with retry in your domain logic.

Recommended Retry Design

Retry only transport failures (timeout/network/5xx) with exponential backoff. Do not blindly retry business errors (4xx) without fixing request payload or permissions.

Go Live Runbook

  1. Keep the same API host and verify all examples/routes still match your code.
  2. Swap credentials to sk_live_* key in production secret store.
  3. Validate connected production phone status via GET /v1/phone-numbers/:id/status.
  4. Send a low-risk real message and verify status progression.
  5. Enable webhook verification and alerting for failed deliveries.