{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://barncleuth.com/schema/feed-v2.json",
  "title": "Barncleuth feed, version 2",
  "description": "The status file your hub publishes. Only the date and the public block can be read. The rest is in two envelopes, and payload below describes what's inside them.",
  "type": "object",
  "required": [
    "v",
    "generated",
    "public",
    "feed_enc",
    "feed_sealed"
  ],
  "additionalProperties": false,
  "patternProperties": {
    "^(inbox|trading|accounts)_(enc)$": {
      "$ref": "#/definitions/passphrase_envelope"
    },
    "^(inbox|trading|accounts)_(sealed)$": {
      "$ref": "#/definitions/key_envelope"
    }
  },
  "properties": {
    "v": {
      "const": 2,
      "description": "The feed version."
    },
    "generated": {
      "type": "string",
      "description": "When the hub made this file."
    },
    "public": {
      "type": "object",
      "description": "The only numbers anyone can read without a key.",
      "additionalProperties": false,
      "required": [
        "desks_working",
        "desks_total",
        "brands",
        "posts_7d"
      ],
      "properties": {
        "desks_working": {
          "type": "integer",
          "minimum": 0,
          "description": "Bots that reported in the last 2 hours and aren't blocked."
        },
        "desks_total": {
          "type": "integer",
          "minimum": 0,
          "description": "Bots on the team, leaving out retired ones."
        },
        "brands": {
          "type": "integer",
          "minimum": 0,
          "description": "How many brands the team runs."
        },
        "posts_7d": {
          "type": "integer",
          "minimum": 0,
          "description": "Posts in the last 7 days."
        }
      }
    },
    "feed_enc": {
      "$ref": "#/definitions/passphrase_envelope",
      "description": "The payload, locked with your passphrase. The board's \"Locked: enter passphrase\" box opens this in the browser."
    },
    "feed_sealed": {
      "$ref": "#/definitions/key_envelope",
      "description": "The payload, locked to the server's public key at /feed-seal-key. The server opens this for signed-in people."
    }
  },
  "definitions": {
    "passphrase_envelope": {
      "type": "object",
      "description": "PBKDF2-SHA256 with 310,000 rounds gives an AES-256-GCM key. Every value is base64.",
      "additionalProperties": false,
      "required": [
        "v",
        "salt",
        "iv",
        "data"
      ],
      "properties": {
        "v": {
          "const": 1,
          "description": "The envelope's version."
        },
        "salt": {
          "type": "string",
          "description": "16 random bytes."
        },
        "iv": {
          "type": "string",
          "description": "12 random bytes."
        },
        "data": {
          "type": "string",
          "description": "The JSON, encrypted, with its tag at the end."
        }
      }
    },
    "key_envelope": {
      "type": "object",
      "description": "A fresh AES-256-GCM key, wrapped with RSA-OAEP-SHA256. Every value is base64 except alg and kid.",
      "additionalProperties": false,
      "required": [
        "v",
        "alg",
        "kid",
        "ek",
        "iv",
        "data"
      ],
      "properties": {
        "v": {
          "const": 1,
          "description": "The envelope's version."
        },
        "alg": {
          "const": "RSA-OAEP-256+A256GCM",
          "description": "How it's locked."
        },
        "kid": {
          "type": "string",
          "pattern": "^[0-9a-f]{16}$",
          "description": "The first 16 hex characters of the SHA-256 of the public key."
        },
        "ek": {
          "type": "string",
          "description": "The AES key, wrapped."
        },
        "iv": {
          "type": "string",
          "description": "12 random bytes."
        },
        "data": {
          "type": "string",
          "description": "The JSON, encrypted, with its tag at the end."
        }
      }
    },
    "payload": {
      "type": "object",
      "description": "What's inside both envelopes. A version 1 feed is this same object, published without the envelopes. Every section is optional, and anything you leave out shows as \"Not reported\", never as zero.",
      "properties": {
        "generated": {
          "type": "string",
          "description": "When the hub made this file."
        },
        "schema_version": {
          "type": [
            "integer",
            "string"
          ],
          "description": "Your own version marker. The board warns if it's newer than it understands."
        },
        "desks": {
          "type": "array",
          "description": "Every bot on the team.",
          "items": {
            "type": "object",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "The bot's id, such as publisher."
              },
              "state": {
                "enum": [
                  "LIVE",
                  "DONE",
                  "BLOCKED",
                  "HOLD"
                ],
                "description": "What it's doing."
              },
              "last_ts": {
                "type": "string",
                "description": "When it last reported."
              },
              "has_input": {
                "type": "boolean",
                "description": "True if work is waiting for it."
              },
              "paused": {
                "enum": [
                  "budget"
                ],
                "description": "Why the desk is paused, when it wasn't a person: budget means auto-pause stopped it at 100% of its budget. Send state HOLD with it, and the board shows Paused: budget."
              }
            }
          }
        },
        "meta": {
          "type": "object",
          "description": "Names for what's on the board.",
          "properties": {
            "brands": {
              "type": "array",
              "description": "The brands the team runs.",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            },
            "desks": {
              "type": "array",
              "description": "A display name for each bot.",
              "items": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "last_at": {
                    "type": "string",
                    "description": "A date and time, ISO 8601 with its timezone."
                  }
                }
              }
            },
            "site": {
              "type": "object",
              "description": "Your board's name, logo and accent colour."
            }
          }
        },
        "posts": {
          "type": "array",
          "description": "What went live.",
          "items": {
            "type": "object",
            "required": [
              "brand"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "url": {
                "type": "string",
                "description": "A public link to the post."
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "views": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Views, if measured."
              },
              "thumb": {
                "type": "string",
                "description": "A thumbnail address."
              },
              "title": {
                "type": "string"
              },
              "desk": {
                "type": "string",
                "description": "The bot that posted it."
              },
              "views_ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "views_src": {
                "type": "string",
                "description": "Where the view count came from."
              },
              "avg_view_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Average percent of the video watched."
              },
              "made_for_kids": {
                "type": "boolean"
              },
              "ai_label": {
                "type": "boolean",
                "description": "True if it carries an AI label."
              },
              "original": {
                "type": "boolean",
                "description": "True if it's original work."
              }
            }
          }
        },
        "schedule": {
          "type": "array",
          "description": "What's coming up.",
          "items": {
            "type": "object",
            "required": [
              "brand"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "when": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "state": {
                "enum": [
                  "scheduled",
                  "posted",
                  "live",
                  "missed",
                  "queued",
                  "hold"
                ]
              }
            }
          }
        },
        "handoffs": {
          "type": "array",
          "description": "Work moving between bots.",
          "items": {
            "type": "object",
            "required": [],
            "properties": {
              "from": {
                "type": "string",
                "description": "The bot passing work on."
              },
              "to": {
                "type": "string",
                "description": "The bot receiving it."
              },
              "item": {
                "type": "string",
                "description": "What's being passed."
              },
              "state": {
                "type": "string"
              },
              "brand": {
                "type": "string"
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "count": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "How many, if it's a count."
              }
            }
          }
        },
        "approvals": {
          "type": "array",
          "description": "What's waiting for a person.",
          "items": {
            "type": "object",
            "required": [],
            "properties": {
              "desk": {
                "type": "string"
              },
              "item": {
                "type": "string",
                "description": "What needs a yes."
              },
              "brand": {
                "type": "string"
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "count": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "made_for_kids": {
                "type": "boolean"
              },
              "ai_label": {
                "type": "boolean"
              },
              "original": {
                "type": "boolean"
              }
            }
          }
        },
        "blockers": {
          "type": "array",
          "description": "What's stuck, in short.",
          "items": {
            "type": "object",
            "required": [],
            "properties": {
              "desk": {
                "type": "string"
              },
              "brand": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "reason": {
                "enum": [
                  "account check",
                  "verification",
                  "stock",
                  "review"
                ]
              },
              "who": {
                "type": "string",
                "description": "Who can clear it."
              },
              "needs": {
                "type": "string",
                "description": "What it needs, in plain words."
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "count": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              }
            }
          }
        },
        "blocked": {
          "type": "array",
          "description": "What's stuck, with how long and why.",
          "items": {
            "type": "object",
            "required": [
              "desk",
              "state",
              "brand",
              "platform",
              "blocked_since"
            ],
            "properties": {
              "desk": {
                "type": "string"
              },
              "brand": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "state": {
                "enum": [
                  "BLOCKED",
                  "HOLD"
                ]
              },
              "reason": {
                "type": "string",
                "description": "Why, in plain words."
              },
              "waiting_on": {
                "type": "string",
                "description": "owner, external, or desk:<id>."
              },
              "waiting_label": {
                "type": "string",
                "description": "Who it's waiting on, for external."
              },
              "blocked_since": {
                "type": "string",
                "description": "When it got stuck. Rows without this are dropped."
              },
              "ask_ref": {
                "type": "string",
                "description": "The inbox ask this is waiting on."
              }
            }
          }
        },
        "brands": {
          "type": "array",
          "description": "One row per brand per platform.",
          "items": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": "string",
                "description": "The brand, such as Shift Fuel (YouTube)."
              },
              "platform": {
                "type": "string"
              },
              "views": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "likes": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "subs": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Subscribers or followers."
              },
              "history": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "Recent values, for the small chart."
              },
              "stats_ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "views_src": {
                "type": "string"
              },
              "data_through": {
                "type": "string"
              },
              "retention": {
                "type": "string"
              },
              "period": {
                "type": "string"
              },
              "subs_change_7d": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "views_change_7d": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        },
        "counts": {
          "type": "object",
          "description": "Totals, if your hub keeps them.",
          "properties": {
            "desks_by_state": {
              "type": "object"
            },
            "queue": {
              "type": "integer"
            },
            "holds": {
              "type": "integer"
            },
            "asks_open": {
              "type": "integer"
            }
          }
        },
        "usage": {
          "type": "object",
          "description": "The usage meter.",
          "properties": {
            "unit": {
              "type": "string",
              "description": "Such as credits."
            },
            "plan_limit": {
              "type": [
                "number",
                "null"
              ],
              "description": "Your plan's limit. Without it there's no percentage or warning."
            },
            "period_used": {
              "type": [
                "number",
                "null"
              ]
            },
            "period_ends": {
              "type": "string",
              "description": "A date and time, ISO 8601 with its timezone."
            },
            "updated_at": {
              "type": "string",
              "description": "A date and time, ISO 8601 with its timezone."
            },
            "desks": {
              "type": "array",
              "description": "One row per bot.",
              "items": {
                "type": "object",
                "required": [
                  "desk"
                ],
                "properties": {
                  "desk": {
                    "type": "string"
                  },
                  "today": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "week": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "period": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "model": {
                    "type": "string",
                    "description": "The model this bot used, so the Usage tab can price it from your price table."
                  }
                }
              }
            }
          }
        },
        "runs": {
          "type": "array",
          "description": "Run history. Keep about the last 300.",
          "items": {
            "type": "object",
            "required": [
              "desk"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "desk": {
                "type": "string"
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "title": {
                "type": "string",
                "description": "In plain words: no job ids, paths, links or emails."
              },
              "ok": {
                "type": "boolean"
              },
              "error": {
                "type": "string"
              },
              "duration_s": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "brand": {
                "type": "string",
                "description": "The brand the run was for, if it was for one."
              },
              "model": {
                "type": "string",
                "description": "The model the run used."
              },
              "tokens": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Tokens used, for the run's cost estimate."
              },
              "cost_usd": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "What the run cost in US dollars, if you know. It's shown as reported, not estimated."
              },
              "input": {
                "type": "string",
                "description": "A short summary of what went in. Keys, tokens and emails are taken out, and a line with a path or id is hidden."
              },
              "output": {
                "type": "string",
                "description": "A short summary of what came out, cleaned the same way."
              },
              "steps": {
                "type": "array",
                "description": "The run's steps, for the drill-down. Up to 30.",
                "items": {
                  "type": "object",
                  "required": [
                    "name"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "In plain words, like Research or Upload."
                    },
                    "ts": {
                      "type": "string",
                      "description": "A date and time, ISO 8601 with its timezone."
                    },
                    "duration_s": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string",
                      "description": "In plain words: no job ids, paths, links or emails."
                    },
                    "input": {
                      "type": "string"
                    },
                    "output": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "videos": {
          "type": "array",
          "description": "Every video as a card on the Studio board. Without it, the board shows what approvals, schedule and posts report.",
          "items": {
            "type": "object",
            "required": [
              "title",
              "stage"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Stays the same as the video moves."
              },
              "brand": {
                "type": "string"
              },
              "title": {
                "type": "string",
                "description": "In plain words."
              },
              "stage": {
                "enum": [
                  "idea",
                  "script",
                  "visuals",
                  "review",
                  "scheduled",
                  "live"
                ],
                "description": "Where it is now."
              },
              "desk": {
                "type": "string",
                "description": "The bot working on it."
              },
              "ts": {
                "type": "string",
                "description": "When it reached this stage."
              },
              "when": {
                "type": "string",
                "description": "When a scheduled video goes out."
              },
              "cost_aud": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "What it has cost so far, in AUD."
              },
              "ask_id": {
                "type": "string",
                "description": "The ask behind a card in review, so Approve and Reject work from the card."
              },
              "failed": {
                "type": "boolean"
              },
              "error": {
                "type": "string",
                "description": "What went wrong, in plain words."
              },
              "url": {
                "type": "string",
                "description": "A public link, once it is live."
              },
              "views": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Views, if measured."
              },
              "thumb": {
                "type": "string",
                "description": "A preview image address."
              }
            }
          }
        },
        "routines": {
          "type": "array",
          "description": "The calendar.",
          "items": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "desk": {
                "type": "string"
              },
              "kind": {
                "enum": [
                  "job",
                  "post"
                ]
              },
              "days": {
                "description": "daily, weekdays, or a list such as [\"mon\", \"thu\"]."
              },
              "time": {
                "type": "string",
                "description": "HH:MM, Sydney time."
              },
              "next_run": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              },
              "brand": {
                "type": "string"
              },
              "optional": {
                "type": "boolean",
                "description": "True if low-usage mode can hold it."
              }
            }
          }
        },
        "credits": {
          "type": "array",
          "description": "The older form of usage: a weekly figure per bot.",
          "items": {
            "type": "object",
            "required": [
              "desk"
            ],
            "properties": {
              "desk": {
                "type": "string"
              },
              "week": {
                "type": "string"
              },
              "used": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        },
        "sponsors": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "sponsor"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "sponsor": {
                "type": "string"
              },
              "stage": {
                "enum": [
                  "lead",
                  "contacted",
                  "replied",
                  "deal",
                  "paid"
                ]
              },
              "value_aud": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              }
            }
          }
        },
        "shop": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "product"
            ],
            "properties": {
              "store": {
                "enum": [
                  "etsy",
                  "gumroad"
                ]
              },
              "product": {
                "type": "string"
              },
              "sales_7d": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "revenue_7d_aud": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "ts": {
                "type": "string",
                "description": "A date and time, ISO 8601 with its timezone."
              }
            }
          }
        },
        "revenue": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "brand"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "month": {
                "type": "string"
              },
              "source": {
                "enum": [
                  "sponsor",
                  "shop",
                  "platform"
                ]
              },
              "aud": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        },
        "coverage": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "brand",
              "state"
            ],
            "properties": {
              "brand": {
                "type": "string"
              },
              "platform": {
                "type": "string"
              },
              "state": {
                "enum": [
                  "live",
                  "stale",
                  "missing"
                ]
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
