{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/instasent/json-schema/instasent-audience-context-schema.json",
  "title": "AudienceContext",
  "description": "The audience snapshot of one project. Every section is an object reduced from the\ncorresponding Product API read; any of them except `audience` may instead be\n`{pending: true, reason}` when its underlying cache is not warm yet or could not be\nread. The shape of each section may grow; read it loosely.\n",
  "x-generated": "2026-10-28",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/product.openapi.yaml#/components/schemas/AudienceContext",
  "type": "object",
  "properties": {
    "audience": {
      "allOf": [
        {
          "$ref": "#/$defs/AudienceOverview"
        }
      ],
      "nullable": true,
      "description": "Contacts and reach per channel and policy, as in `audience/overview`. Null when the audience index cannot be read."
    },
    "attributes": {
      "type": "object",
      "description": "The attributes that carry data, best filled first (at most 40), each with\nits fill rate, kind (`system` or `custom`) and top values when it earns\nthem, plus `declaredCount`, `withDataCount`, `customCount`, `sampling`,\n`computedAt`, `computedAgo` and `summary`. An attribute nobody has filled\nis never listed. On a large project whose coverage was never profiled it is\n`{pending: true, reason, warmedBy}`.\n",
      "additionalProperties": true
    },
    "events": {
      "type": "object",
      "description": "The event types received in the plan window (`window.days`), each with\n`uid`, `label`, `count`, `lastSeenAt` and `sources[{name, count,\npreferred}]` in rank order, where `preferred` marks the source a trigger\nshould listen to. `platformEventTypes[{uid, count}]` lists Instasent's own\nactivity received in the window (update, subscribe, flow events), which has\nno source to choose. Also `declaredCount`, `receivedCount`,\n`neverReceivedCount`, `computedAt`, `computedAgo` and `summary`.\n",
      "additionalProperties": true
    },
    "sales": {
      "type": [
        "object",
        "null"
      ],
      "description": "What this project sells: the top five `categories`, `collections`, `products` and `tags`\n(`{value, count}`, count being orders that carry the value) of the\n`ecommerce_order_create` events from the preferred source only, so a\nrelayed copy of the orders is not counted twice. Also `event`, `source`,\n`orders`, `window.days`, `sampled`, `computedAt` and `computedAgo`. Values\nare names from the shop's catalogue, never contact data; prices and\nquantities are not included. Null when no order arrived in the window.\n",
      "additionalProperties": true
    },
    "segments": {
      "type": "object",
      "description": "The customer's own segments (at most 60), each with the size the platform\nalready holds; a null size means not computed yet, never empty. Also\n`declaredCount`, `sizedCount`, `returnedCount` and `summary`.\n",
      "additionalProperties": true
    },
    "freshness": {
      "type": "object",
      "description": "Age of each section in words (`audience`, `attributes`, `events`, `sales`, `segments`); null when the section has no single age.",
      "additionalProperties": {
        "type": [
          "string",
          "null"
        ]
      }
    },
    "summary": {
      "type": "string",
      "description": "One sentence with the headline of every section."
    }
  },
  "required": [
    "audience",
    "attributes",
    "events",
    "sales",
    "segments",
    "freshness",
    "summary"
  ],
  "additionalProperties": true,
  "$defs": {
    "AudienceChannelReach": {
      "type": "object",
      "description": "How many contacts one channel can reach, under the project's policy and under every other.",
      "properties": {
        "reachable": {
          "type": "integer",
          "description": "Contacts that would receive on this channel under the project's own\npolicy: the number that answers \"how many can I reach\". Absent when the\npolicy has no entry in the ladder.\n"
        },
        "policy": {
          "type": "string",
          "description": "The consent policy this project applies on this channel, which is the one `reachable` counts under.",
          "enum": [
            "basic",
            "no-opt-out",
            "opt-in"
          ]
        },
        "byPolicy": {
          "type": "object",
          "description": "The full ladder: what each policy would reach on this channel. Read it as\n\"under opt-in I would reach N\", never as a bare number, and use it to show\nwhat tightening or loosening the policy would cost or gain.\n",
          "additionalProperties": {
            "type": "integer"
          }
        }
      },
      "required": [
        "policy",
        "byPolicy"
      ]
    },
    "AudienceOverview": {
      "type": "object",
      "description": "The audience of a project by channel and consent policy. `contacts` is every\ncontact in the project; each channel block says how many of them can actually\nreceive a message under the project's own policy, and what every other policy\nwould reach. Only channels the project can send on appear.\n",
      "properties": {
        "contacts": {
          "type": "integer",
          "description": "Total contacts in the project's audience."
        },
        "computedAgo": {
          "type": "string",
          "description": "Age of the counters in words. The counters are cached per project with a\nTTL that grows with the audience's size, from one minute for small\naudiences up to twelve hours for very large ones; narrate the figure with\nthis age rather than as a live total.\n"
        },
        "sms": {
          "$ref": "#/$defs/AudienceChannelReach"
        },
        "rcs": {
          "$ref": "#/$defs/AudienceChannelReach"
        }
      },
      "required": [
        "contacts",
        "computedAgo"
      ],
      "additionalProperties": true
    }
  }
}