{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/instasent/json-schema/instasent-flow-template-schema.json",
  "title": "FlowTemplate",
  "x-generated": "2026-10-28",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/product.openapi.yaml#/components/schemas/FlowTemplate",
  "type": "object",
  "properties": {
    "key": {
      "type": "string"
    },
    "title": {
      "type": "string"
    },
    "description": {
      "type": "string"
    },
    "categories": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "icon": {
      "type": "string",
      "description": "Lucide icon name in kebab-case, fixed per template in the catalog."
    },
    "requires": {
      "type": "array",
      "description": "What the template needs from its trigger source; empty when nothing. `realtimeEvents`, its trigger and exits must arrive as they happen. On a source that delivers in batches the server lengthens the template's first wait, and each source of the item says so in `timingWarning`.",
      "items": {
        "type": "string",
        "enum": [
          "realtimeEvents"
        ]
      }
    },
    "recommended": {
      "type": "boolean",
      "description": "True for at most three templates, best first, that are `received` and not `used` yet. The same set the flow-template attention recommendation surfaces."
    },
    "integrations": {
      "type": "array",
      "description": "The integrations that can send one of the trigger candidates, connected first, then by name. Empty for templates triggered by Instasent's own events, which no single integration unlocks.",
      "items": {
        "type": "object",
        "properties": {
          "integration": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "connected": {
            "type": "boolean"
          },
          "datasourceId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The project's source of this integration the trigger listens to when the user chooses this integration, also when it is not one of `trigger.sourceOptions` (it declares the event and has not sent it yet). With several sources of the integration, the first of those that declare a trigger candidate, ranked as the source options are. Accepted as `datasourceId` on POST /flow. Null when the integration is not connected or no candidate resolves on it."
          },
          "eventType": {
            "type": [
              "string",
              "null"
            ],
            "description": "The trigger event resolved on `datasourceId` alone, the one a draft built on it listens to. Null exactly when `datasourceId` is null."
          },
          "delivery": {
            "$ref": "#/$defs/FlowTemplateSourceDelivery"
          },
          "timingWarning": {
            "allOf": [
              {
                "$ref": "#/$defs/FlowTemplateTimingWarning"
              }
            ],
            "nullable": true
          }
        }
      }
    },
    "channels": {
      "type": "array",
      "description": "Send modes this project can use for the template. `sms`, plus `rcs` when the project has an active RCS sender.",
      "items": {
        "type": "string",
        "enum": [
          "sms",
          "rcs"
        ]
      }
    },
    "channel": {
      "type": "string",
      "description": "The mode `payload` is built in (the `channel` query parameter).",
      "enum": [
        "sms",
        "rcs"
      ]
    },
    "relevance": {
      "type": "string",
      "enum": [
        "received",
        "available",
        "unavailable"
      ]
    },
    "trigger": {
      "type": "object",
      "properties": {
        "eventType": {
          "type": "string"
        },
        "datasourceId": {
          "type": [
            "string",
            "null"
          ],
          "description": "The source the draft's trigger listens to, Instasent's own events (`update`) included; null on a schedule and when nothing can trigger the template."
        },
        "datasource": {
          "type": [
            "object",
            "null"
          ],
          "description": "`datasourceId` named, whatever its state; null exactly when `datasourceId` is null. It names the default source also when it is not one of `sourceOptions` (an integration that only declares the event and has not sent it yet).",
          "properties": {
            "id": {
              "type": "string"
            },
            "name": {
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "type": [
                "string",
                "null"
              ]
            },
            "integration": {
              "type": [
                "string",
                "null"
              ]
            },
            "delivery": {
              "$ref": "#/$defs/FlowTemplateSourceDelivery"
            },
            "timingWarning": {
              "allOf": [
                {
                  "$ref": "#/$defs/FlowTemplateTimingWarning"
                }
              ],
              "nullable": true
            }
          }
        },
        "candidates": {
          "type": "array",
          "description": "The event types that can trigger the template, in preference order.",
          "items": {
            "type": "string"
          }
        },
        "candidateStates": {
          "type": "array",
          "description": "Each of `candidates`, in the same order, resolved on the project's sources as the chosen trigger is.",
          "items": {
            "type": "object",
            "properties": {
              "eventType": {
                "type": "string"
              },
              "state": {
                "type": "string",
                "enum": [
                  "received",
                  "available",
                  "unavailable"
                ]
              },
              "datasourceId": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "lastSeenAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        },
        "lastSeenAt": {
          "type": [
            "string",
            "null"
          ],
          "format": "date-time",
          "description": "Latest arrival of the trigger event in the counts window, whatever `relevance` says. With `unavailable`, a value means the event is stale (it arrived, but more than 30 days ago), not unknown."
        },
        "sourceOptions": {
          "type": "array",
          "description": "The sources that sent the trigger event and the trigger can listen to; with the default `trigger.datasourceId`, the ids `POST /flow` accepts as `datasourceId` for this template. The recommended one first (what the server picks when `POST /flow` has no `datasourceId`; `trigger.datasourceId` shows it too, except under an `integration` filter for an integration not connected), then the server's ranking (received, then available, then sent only long ago; the template's candidate order; Instasent's own source last; then the most authoritative, fresher, busier). A source that never sent the event is not an option, whatever it declares (an API source that has not sent it yet, an integration that only declares it), since nothing proves the event will ever come from it. Empty on a schedule template and when no source sent the event, even when `trigger.datasourceId` pins an integration that declares it.",
          "items": {
            "type": "object",
            "properties": {
              "datasourceId": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "integration": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "eventType": {
                "type": "string",
                "description": "The candidate event the flow starts on with this source; it can differ between sources when the template has several candidates."
              },
              "state": {
                "type": "string",
                "enum": [
                  "received",
                  "stale"
                ],
                "description": "`received`: this source sent the event in the last 30 days; `stale`: it sent it earlier in the counts window, not in the last 30 days (`lastSeenAt` says when)."
              },
              "count": {
                "type": "integer",
                "description": "Events of `eventType` this source sent in the counts window."
              },
              "lastSeenAt": {
                "type": "string",
                "format": "date-time"
              },
              "recommended": {
                "type": "boolean",
                "description": "True for at most one, the first; none when the default source is not an option (see `trigger.datasource`)."
              },
              "delivery": {
                "$ref": "#/$defs/FlowTemplateSourceDelivery"
              },
              "timingWarning": {
                "allOf": [
                  {
                    "$ref": "#/$defs/FlowTemplateTimingWarning"
                  }
                ],
                "nullable": true
              }
            }
          }
        }
      }
    },
    "datasources": {
      "type": "array",
      "items": {
        "type": "object",
        "additionalProperties": true
      }
    },
    "score": {
      "type": "integer"
    },
    "scoring": {
      "type": "array",
      "description": "One English sentence per reason the template scored what it did.",
      "items": {
        "type": "object",
        "properties": {
          "signal": {
            "type": "string"
          },
          "delta": {
            "type": "integer"
          },
          "text": {
            "type": "string"
          }
        }
      }
    },
    "used": {
      "type": "boolean"
    },
    "triggerKind": {
      "type": "string",
      "description": "What starts the flow. `event`, an event a contact sends; `schedule`, a run on a schedule over an audience.",
      "enum": [
        "event",
        "schedule"
      ]
    },
    "variants": {
      "type": "array",
      "description": "The channels the template is written for. Distinct from `channels`, the modes this project can use; an `rcs` mode without an `rcs` variant wraps the SMS messages as RCS.",
      "items": {
        "type": "string",
        "enum": [
          "sms",
          "rcs"
        ]
      }
    },
    "guidance": {
      "type": "string",
      "description": "In English, what the flow does and how to adapt it, for the graph served in `channel`."
    },
    "audience": {
      "type": "object",
      "description": "Who the trigger lets in. With `resolved` = `segment`, the trigger is limited to the recommended segment; with `all`, to every contact with a mobile phone (no segment recommended, or the project does not have it). `intent` describes the audience the template is meant for, to refine it.",
      "properties": {
        "segment": {
          "type": [
            "string",
            "null"
          ],
          "description": "Uid of the recommended segment, if any."
        },
        "name": {
          "type": [
            "string",
            "null"
          ],
          "description": "The segment's name, when it is applied."
        },
        "resolved": {
          "type": "string",
          "enum": [
            "segment",
            "all"
          ]
        },
        "intent": {
          "type": [
            "string",
            "null"
          ]
        }
      }
    },
    "inputs": {
      "type": "array",
      "description": "Values only the customer knows that the messages need. Every input is required or has a default.",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The key to send in `inputs` on `POST /flow`, and the `<key>` of `$input:<key>` in the text."
          },
          "kind": {
            "type": "string",
            "enum": [
              "discountCode",
              "discountPercent",
              "reviewUrl",
              "storeUrl"
            ],
            "description": "`discountCode`, a code the customer types at checkout (one short line); `discountPercent`, what the discount takes off, a whole number from 1 to 99 without the % sign; `reviewUrl`, the page to leave a review, as a full http(s) url; `storeUrl`, the store's website, as a full http(s) url (defaults to the brand's website)."
          },
          "required": {
            "type": "boolean"
          },
          "default": {
            "type": [
              "string",
              "null"
            ],
            "description": "The value already filled in `payload` for this project, if any."
          }
        }
      }
    },
    "pending": {
      "type": "array",
      "description": "The input keys still without a value, written as `$input:<key>` in `payload`. A draft saved with any of them does not validate until it is filled.",
      "items": {
        "type": "string"
      }
    },
    "payload": {
      "$ref": "#/$defs/FlowDraft"
    }
  },
  "$defs": {
    "FlowDraft": {
      "type": "object",
      "description": "A flow graph in the version export shape. Steps chain through `nextStep`; branch steps carry `paths`; the trigger carries `exitSteps`. Relations (`eventDatasource`, `channel.options[].sender`) are ids of THIS project's datasources and senders; any other is reported as an error. Only the node types in `stepClass` may be placed through the API.",
      "properties": {
        "name": {
          "type": "string"
        },
        "description": {
          "type": "string"
        },
        "trigger": {
          "type": "object",
          "properties": {
            "stepClass": {
              "type": "string",
              "enum": [
                "TRIGGER_CONTACT_EVENT"
              ]
            },
            "eventType": {
              "type": "string"
            },
            "eventDatasource": {
              "type": [
                "string",
                "null"
              ],
              "description": "The datasource the trigger listens to. Set it to the event's preferred source (POST /event/volumes); null only for Instasent's own events."
            },
            "eventQueryFilter": {
              "type": "object",
              "additionalProperties": true
            },
            "maxActivationsPerContact": {
              "type": "integer",
              "enum": [
                0,
                1
              ],
              "default": 0,
              "description": "`1`: a contact enters this flow only once ever, counting every version of the flow. `0`: a contact can enter again, as often as `minSecBetweenActivationsPerContact` allows. There is no \"at most N times\"."
            },
            "minSecBetweenActivationsPerContact": {
              "type": "integer",
              "minimum": 60,
              "maximum": 10000000,
              "default": 300,
              "description": "Minimum seconds between two entries of the same contact into this flow, counting every version. Has no effect when `maxActivationsPerContact` is `1`, since the contact never enters again."
            },
            "maxConcurrentActiveFlowsPerContact": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5,
              "description": "How many executions of THIS flow one contact may have running at the same time. It does not limit how many different flows a contact is in."
            },
            "maxActivationsPerHour": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000,
              "description": "Entries into this flow per hour, all contacts together. When omitted, the platform default applies. It never takes effect above the project's plan ceiling."
            },
            "maxActivationsPerDay": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100000,
              "description": "Entries into this flow per day, all contacts together. When omitted, the platform default applies. It never takes effect above the project's plan ceiling."
            },
            "exitSteps": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/FlowDraftStep"
              }
            },
            "nextStep": {
              "$ref": "#/$defs/FlowDraftStep"
            }
          },
          "additionalProperties": true,
          "required": [
            "stepClass",
            "eventType"
          ]
        }
      },
      "required": [
        "trigger"
      ]
    },
    "FlowDraftStep": {
      "type": "object",
      "properties": {
        "stepClass": {
          "type": "string",
          "enum": [
            "ACTION_SEQUENTIAL_SEND",
            "ACTION_SEQUENTIAL_DELAY",
            "ACTION_SEQUENTIAL_EXIT_TRIGGER_EVENT",
            "ACTION_SEQUENTIAL_GOAL",
            "ACTION_SEQUENTIAL_END",
            "ACTION_SEQUENTIAL_CONTACT_LIST",
            "ACTION_SEQUENTIAL_CONTACT_TAG",
            "ACTION_SEQUENTIAL_SUBSCRIPTION_MANAGE",
            "ACTION_BRANCH_ABTEST",
            "ACTION_BRANCH_CHANNEL_CAPABILITY",
            "ACTION_BRANCH_CONTACT_ATTRIBUTE",
            "ACTION_BRANCH_CONTACT_CONSENT",
            "ACTION_BRANCH_CONTACT_EVENT_SEARCH",
            "ACTION_BRANCH_CONTACT_EVENT_WAIT",
            "ACTION_BRANCH_CONTACT_LIST",
            "ACTION_BRANCH_CONTACT_SEGMENT",
            "ACTION_BRANCH_CONTACT_TAG",
            "ACTION_BRANCH_MESSAGE_DELIVERY_WAIT",
            "ACTION_BRANCH_SMART_DELAY",
            "ACTION_BRANCH_TIMEZONE_DELAY",
            "ACTION_BRANCH_TRIGGER_EVENT"
          ]
        }
      },
      "additionalProperties": true,
      "required": [
        "stepClass"
      ]
    },
    "FlowTemplateSourceDelivery": {
      "type": "object",
      "description": "How a trigger source delivers its events. `realtime`, as they happen (webhooks, the Ingest API, the API, Instasent's own events). `batch`, pulled by the periodic integration sync every `syncIntervalSeconds`, the interval that sync applies for this project's organization and plan; on an integration not connected yet, the interval the plan would give it.",
      "properties": {
        "mode": {
          "type": "string",
          "enum": [
            "realtime",
            "batch"
          ]
        },
        "syncIntervalSeconds": {
          "type": [
            "integer",
            "null"
          ],
          "description": "Seconds between two syncs of a `batch` source; null on a `realtime` one, and when the plan does not sync that source."
        }
      }
    },
    "FlowTemplateTimingWarning": {
      "type": "object",
      "description": "Present when the template requires realtime events and the source delivers in batches. Waits after the trigger need to be longer than the sync interval, or a purchase made in the meantime arrives after the message.",
      "properties": {
        "syncIntervalSeconds": {
          "type": "integer"
        },
        "addedWaitSeconds": {
          "type": "integer",
          "description": "What the server adds to the template's first wait when the flow is built on this source (`POST /flow` with its `datasourceId`, or the item's `payload` on its default source). On a fixed wait, the interval, at most 24 hours, rounded up to whole 15 minutes; on a smart delay, what moves its earliest send there; 0 when the first wait already covers it."
        }
      }
    }
  }
}