{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/honkio-ca/json-schema/honkio-ca-account-document-schema.json",
  "title": "AccountDocument",
  "description": "The account with its rent position, number allowance and active API keys, from GET /v1/accounts/{id}. GET /v1/accounts/me returns it with caller added.",
  "x-generated": "2026-10-30",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/honkio-ca-openapi.json#/components/schemas/AccountDocument",
  "type": "object",
  "additionalProperties": true,
  "required": [
    "account",
    "api_keys",
    "features",
    "comped"
  ],
  "properties": {
    "account": {
      "allOf": [
        {
          "$ref": "#/$defs/Account"
        },
        {
          "type": "object",
          "additionalProperties": true,
          "required": [
            "monthly_rent_cents",
            "low_balance",
            "rent_owed_cents",
            "phone_number_limit",
            "phone_numbers_used",
            "inbound_sms_daily_cap",
            "inbound_sms_received_24h"
          ],
          "properties": {
            "monthly_rent_cents": {
              "type": "integer",
              "description": "Monthly rent owed on every number not yet released (ACTIVE and SUSPENDED)."
            },
            "low_balance": {
              "type": "boolean",
              "description": "True when the credit balance would not cover next month's phone-number rent. Always false while the account is comped."
            },
            "rent_owed_cents": {
              "type": "integer",
              "description": "Rent owed (CAD cents) on a number that was released while suspended, 0 when nothing is owed. While it is above 0, paid sends and number purchases are refused with RENT_OWED; a top-up pays it first. Not enforced while the account is comped."
            },
            "phone_number_limit": {
              "type": "integer",
              "description": "Maximum phone numbers this account may hold."
            },
            "phone_numbers_used": {
              "type": "integer",
              "description": "Numbers currently held (ACTIVE and SUSPENDED)."
            },
            "email_domain_limit": {
              "type": "integer",
              "description": "Maximum live sending domains this account may hold. Present only while email is available."
            },
            "email_domains_used": {
              "type": "integer",
              "description": "Live sending domains held (every status but FAILED). Present only while email is available."
            },
            "inbound_email_daily_cap": {
              "type": "integer",
              "description": "Received emails this account may accept per rolling 24 h before further mail is stored rejected and uncharged (0 = unlimited); the account's own override, or the platform default. Present only while email is available."
            },
            "inbound_email_received_24h": {
              "type": "integer",
              "description": "Received emails counted against the cap above, in the current rolling 24 h window. Present only while email is available."
            },
            "inbound_email_daily_mb_cap": {
              "type": "integer",
              "description": "Billable MB of received email (each message's started MB above the first, the part priced per MB) this account may accept per rolling 24 h; a message that would go past it is stored rejected (daily_mb_cap) and uncharged (0 = unlimited). The account's own override, or the platform default. Present only while email is available."
            },
            "inbound_email_mb_received_24h": {
              "type": "integer",
              "description": "Billable MB counted against the MB cap above, in the current rolling 24 h window. Present only while email is available."
            },
            "inbound_sms_daily_cap": {
              "type": "integer",
              "description": "Received texts this account may accept per rolling 24 h before further texts are stored uncharged and not delivered as message.received (0 = unlimited); the account's own override, or the platform default."
            },
            "inbound_sms_received_24h": {
              "type": "integer",
              "description": "Received texts counted against the cap above, in the current rolling 24 h window."
            }
          }
        }
      ]
    },
    "api_keys": {
      "type": "array",
      "description": "The account's active USER keys (dashboard session keys are not listed).",
      "items": {
        "allOf": [
          {
            "$ref": "#/$defs/ApiKey"
          },
          {
            "required": [
              "defaultDeny",
              "permissions",
              "email_domain_ids",
              "oauth_grant"
            ]
          }
        ]
      }
    },
    "features": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "email"
      ],
      "description": "Platform capabilities currently available to this account.",
      "properties": {
        "email": {
          "type": "boolean",
          "description": "True when the email product is live; false while it is still gated."
        }
      }
    },
    "comped": {
      "type": [
        "object",
        "null"
      ],
      "additionalProperties": true,
      "required": [
        "since",
        "month_cost_cents",
        "threshold_cents"
      ],
      "description": "Set while HonkIO provides this account free of charge (comped): nothing is charged to the balance, which is kept as it was. Null otherwise.",
      "properties": {
        "since": {
          "type": "string",
          "format": "date-time",
          "description": "When the account was comped."
        },
        "month_cost_cents": {
          "type": "integer",
          "description": "What this account's usage has cost HonkIO this calendar month (Eastern time), in CAD cents."
        },
        "threshold_cents": {
          "type": "integer",
          "description": "The monthly usage, in CAD cents, past which the owner is emailed, at most once a month. Sending continues past it."
        }
      }
    }
  },
  "$defs": {
    "Account": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "id",
        "name",
        "email",
        "stripe_customer_id",
        "email_verified_at",
        "status",
        "balance_cents",
        "verified_phone",
        "phone_verified_at",
        "email_undeliverable",
        "email_undeliverable_at",
        "email_undeliverable_reason",
        "terms_accepted_at",
        "terms_version",
        "created_at"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "email": {
          "type": "string"
        },
        "stripe_customer_id": {
          "type": [
            "string",
            "null"
          ]
        },
        "email_verified_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "status": {
          "type": "string",
          "enum": [
            "PENDING_PAYMENT",
            "ACTIVE",
            "SUSPENDED",
            "CLOSED"
          ]
        },
        "balance_cents": {
          "type": "integer",
          "description": "Credit balance in cents CAD."
        },
        "verified_phone": {
          "type": [
            "string",
            "null"
          ]
        },
        "phone_verified_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "email_undeliverable": {
          "type": "boolean",
          "description": "True when platform email to this address is bouncing or being marked as spam."
        },
        "email_undeliverable_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "The bounce or complaint that made the address unreachable."
        },
        "email_undeliverable_reason": {
          "type": [
            "string",
            "null"
          ],
          "description": "BOUNCE or COMPLAINT."
        },
        "terms_accepted_at": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "When the owner accepted the Terms of Service and Privacy Policy."
        },
        "terms_version": {
          "type": [
            "string",
            "null"
          ],
          "description": "The Terms version (last-updated date) that was accepted."
        },
        "created_at": {
          "type": "string",
          "format": "date-time"
        }
      },
      "description": "An account, as signup and PATCH /v1/accounts/{id} return it."
    },
    "ApiKey": {
      "type": "object",
      "additionalProperties": true,
      "required": [
        "id",
        "keyPrefix",
        "mode",
        "label",
        "createdAt",
        "lastUsedAt"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "keyPrefix": {
          "type": "string",
          "description": "Non-secret prefix for display and identification."
        },
        "mode": {
          "type": "string",
          "enum": [
            "LIVE",
            "TEST"
          ]
        },
        "label": {
          "type": [
            "string",
            "null"
          ]
        },
        "createdAt": {
          "type": "string",
          "format": "date-time"
        },
        "lastUsedAt": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time"
        },
        "defaultDeny": {
          "type": "boolean",
          "description": "Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."
        },
        "permissions": {
          "$ref": "#/$defs/ApiKeyPermissions"
        },
        "email_domain_ids": {
          "type": [
            "array",
            "null"
          ],
          "items": {
            "type": "string"
          },
          "description": "The email domains this key may send from (ids from /v1/email-domains): null when unrestricted, [] when restricted to none (its domains were deleted). Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."
        },
        "oauth_grant": {
          "type": "boolean",
          "description": "True when an OAuth connection holds this key: it can be revoked but not rotated (409 CONFLICT, details.reason \"oauth_grant\"). Present on GET /v1/accounts/me and /v1/accounts/{id}; absent from the login response."
        }
      },
      "description": "An active (unrevoked) API key, never the key itself. Field names are camelCase here, unlike the rest of the API."
    },
    "ApiKeyPermissions": {
      "type": "object",
      "description": "Per-resource permissions. Each key is a resource name; value is a string of allowed operation letters: r=read, w=write/create, m=modify/update, d=delete. A missing key denies all access to that resource.",
      "additionalProperties": {
        "type": "string"
      }
    }
  }
}