{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/honkio-ca/json-schema/honkio-ca-sent-message-schema.json",
  "title": "SentMessage",
  "description": "The message a send created, with an estimate of when the carrier will send it.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/honkio-ca-openapi.json#/components/schemas/SentMessage",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "id",
    "direction",
    "from",
    "to",
    "body",
    "body_purged",
    "status",
    "segment_count",
    "mode",
    "is_verification",
    "auto_reply_keyword",
    "cost_cents",
    "error_code",
    "error_message",
    "sent_at",
    "created_at",
    "casl_consent_type",
    "dncl_exemption",
    "delivered_at",
    "estimated_send_at"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "direction": {
      "type": "string",
      "enum": [
        "OUTBOUND",
        "INBOUND"
      ]
    },
    "from": {
      "type": "string"
    },
    "to": {
      "type": "string"
    },
    "body": {
      "type": [
        "string",
        "null"
      ],
      "description": "Null once the body is purged (see body_purged) and on a verification OTP, whose code is never stored."
    },
    "body_purged": {
      "type": "boolean"
    },
    "status": {
      "type": "string",
      "enum": [
        "QUEUED",
        "SENDING",
        "SENT",
        "DELIVERED",
        "FAILED",
        "UNDELIVERED",
        "RECEIVED"
      ]
    },
    "segment_count": {
      "type": [
        "integer",
        "null"
      ],
      "description": "Number of SMS parts the carrier split the body into (inbound: as received). Billing is per part."
    },
    "mode": {
      "type": "string",
      "enum": [
        "LIVE",
        "TEST"
      ],
      "description": "TEST messages are simulated: never sent to a carrier and never billed."
    },
    "is_verification": {
      "type": "boolean",
      "description": "The OTP SMS behind a verification. Its body is never stored."
    },
    "auto_reply_keyword": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "STOP",
        "START",
        "HELP",
        null
      ],
      "description": "Set on the automatic confirmation sent when a recipient texts STOP, START or HELP. You did not send it and are never charged for it. Its body can be null for a few minutes after it is sent, until the carrier reports the text. Null on every other message."
    },
    "cost_cents": {
      "type": "integer",
      "description": "What this message actually cost, in cents CAD: message_cost_cents times the parts the carrier split it into, settled to the carrier's count once it accepts the send (so it can differ from a pre-send estimate). 0 when the carrier refused the send outright (refunded or never charged), and on an automatic STOP/START/HELP confirmation (auto_reply_keyword), which is never charged. A message the carrier accepted but could not deliver keeps its charge. Inbound messages carry the inbound charge. On a TEST message this is what the send would have cost; nothing was charged."
    },
    "error_code": {
      "type": [
        "string",
        "null"
      ],
      "description": "Set on FAILED and UNDELIVERED messages: the carrier's code from the delivery receipt (e.g. 30003), or the platform code for a synchronous rejection (CARRIER_ERROR, INVALID_PHONE_NUMBER, NOT_A_MOBILE_NUMBER, INSUFFICIENT_BALANCE, RENT_OWED). CARRIER_THROTTLED when the carrier asked us to slow down, or dropped the message from the sending number's full queue before trying the recipient: refunded, and safe to send again. CARRIER_LOST when the carrier accepted the message and then lost it: no receipt ever came, and three days later the carrier had no record of it; refunded, and safe to send again with a new Idempotency-Key."
    },
    "error_message": {
      "type": [
        "string",
        "null"
      ],
      "description": "Human-readable reason for a FAILED or UNDELIVERED message, from the carrier when it gave one."
    },
    "sent_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "created_at": {
      "type": "string",
      "format": "date-time"
    },
    "casl_consent_type": {
      "type": [
        "string",
        "null"
      ],
      "enum": [
        "EXPRESS",
        "IMPLIED",
        null
      ],
      "description": "The CASL consent an outbound send went out on. Null for inbound and test-mode messages, carrier-mandated STOP/HELP/START replies, and sends made before this was recorded (18 September 2026)."
    },
    "dncl_exemption": {
      "type": [
        "string",
        "null"
      ],
      "description": "The CRTC Do Not Call List exemption the send relied on, when one applied."
    },
    "delivered_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "When the carrier reported the message delivered to the handset."
    },
    "estimated_send_at": {
      "type": [
        "string",
        "null"
      ],
      "format": "date-time",
      "description": "An estimate of when the carrier will start sending this message, from the parts already waiting on the sending number (a local number sends 10 parts a minute). Close to now when nothing is waiting. Null in test mode and on a retry that returned an earlier send."
    }
  }
}