{
  "openapi": "3.1.0",
  "info": {
    "title": "Just Say It public API",
    "version": "1.0.0",
    "summary": "Service status, the waitlist, a sales inquiry and the creator program.",
    "description": "Just Say It is used in a browser at https://justsayit.ai/app — there is no API for sessions, recordings or notes yet. These public routes are what an agent may do for a person without signing in: check that the service is up, add an address to the waitlist, ask the team about a plan for an organisation, apply to the creator program, and register an approved creator's post. Every answer is JSON; a refusal is `{\"error\": \"<code>\"}` with the HTTP status that fits it.",
    "termsOfService": "https://justsayit.ai/terms",
    "contact": {
      "name": "Just Say It",
      "email": "contact@elav8.ai",
      "url": "https://justsayit.ai/contact"
    },
    "license": {
      "name": "Proprietary — use under the Terms of Service",
      "url": "https://justsayit.ai/terms"
    }
  },
  "servers": [
    {
      "url": "https://justsayit.ai"
    }
  ],
  "security": [],
  "externalDocs": {
    "description": "The developer page",
    "url": "https://justsayit.ai/developers"
  },
  "tags": [
    {
      "name": "status",
      "description": "Whether the service is up."
    },
    {
      "name": "contact",
      "description": "Reaching the people who make it."
    },
    {
      "name": "creators",
      "description": "The creator program: paid for verified views, approved creators only."
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "operationId": "getServiceStatus",
        "tags": [
          "status"
        ],
        "summary": "Is the service up?",
        "description": "Answers 200 with `ok: true` while the service is serving. The object carries operational counters beside `ok`; only `ok` is part of this contract — the rest may change without notice.",
        "responses": {
          "200": {
            "description": "The service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          }
        }
      }
    },
    "/api/waitlist": {
      "post": {
        "operationId": "joinWaitlist",
        "tags": [
          "contact"
        ],
        "summary": "Put an e-mail address on the waitlist.",
        "description": "For when the service is full or a person wants to hear when something opens. The address is kept for that purpose alone and never shown back. At most 5 requests a minute for one address. A form post (`application/x-www-form-urlencoded`) is answered with an HTML page instead of JSON.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "The address to write to."
                  },
                  "variant": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9-]{0,23}$",
                    "description": "Optional: which page or door the request came from; a value of another shape is ignored."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "On the list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "The body was not JSON, or the address is not an e-mail address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "body": {
                    "value": {
                      "error": "body"
                    }
                  },
                  "shape": {
                    "value": {
                      "error": "shape"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Only POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "method": {
                    "value": {
                      "error": "method"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many requests for this address in the last minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "fuse": {
                    "value": {
                      "error": "fuse"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The list could not be written; try again later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "io": {
                    "value": {
                      "error": "io"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/enterprise": {
      "post": {
        "operationId": "contactSales",
        "tags": [
          "contact"
        ],
        "summary": "Ask about a plan for an organisation.",
        "description": "Sends the team an organisation's inquiry — its own cluster, single sign-on, invoicing, custom retention, rooms beyond thirty — and an acknowledgement to the address given; a person replies. One inquiry an hour for one address. A form post is answered with an HTML page instead of JSON.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "company",
                  "size",
                  "region",
                  "needs",
                  "email"
                ],
                "properties": {
                  "company": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "The organisation’s name."
                  },
                  "size": {
                    "type": "string",
                    "enum": [
                      "30-200",
                      "200-1000",
                      "1000+"
                    ],
                    "description": "How many people: `30-200` (30 – 200 people), `200-1000` (200 – 1,000 people), `1000+` (More than 1,000 people)."
                  },
                  "region": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "Where the organisation is — a country, a region, “EU and US”."
                  },
                  "needs": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000,
                    "description": "What it needs, in its own words."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Where the reply goes."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent; a person will reply to the address given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "The body was not JSON, or the named field is missing or too long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "body": {
                    "value": {
                      "error": "body"
                    }
                  },
                  "shape": {
                    "value": {
                      "error": "shape"
                    }
                  },
                  "company": {
                    "value": {
                      "error": "company"
                    }
                  },
                  "size": {
                    "value": {
                      "error": "size"
                    }
                  },
                  "region": {
                    "value": {
                      "error": "region"
                    }
                  },
                  "needs": {
                    "value": {
                      "error": "needs"
                    }
                  },
                  "email": {
                    "value": {
                      "error": "email"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Only POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "method": {
                    "value": {
                      "error": "method"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "An inquiry for this address already arrived in the last hour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "fuse": {
                    "value": {
                      "error": "fuse"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The inquiry could not be recorded; write to the contact address instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "io": {
                    "value": {
                      "error": "io"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/creators/apply": {
      "post": {
        "operationId": "applyAsCreator",
        "tags": [
          "creators"
        ],
        "summary": "Apply to the creator program.",
        "description": "Sends the team a creator's application — their profiles, niche, one to three past videos, audience and, if given, their own follower and view counts — and an acknowledgement to the address given; a person replies either way. Applying is not approval: only a creator approved in writing, under the Creator Agreement, is paid. Only approved creators are paid, for verified views (the rules: https://justsayit.ai/creators). Every link must be https on TikTok, Instagram or YouTube's own hosts. One application a day for one address; at most 10 forms an hour from one network. A form post is answered with an HTML page instead of JSON.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "handles",
                  "niche",
                  "samples",
                  "country",
                  "email",
                  "consent"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "The name the creator posts under."
                  },
                  "handles": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 4,
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "maxLength": 300
                    },
                    "description": "Links to the creator’s profiles on TikTok, Instagram or YouTube."
                  },
                  "niche": {
                    "type": "string",
                    "enum": [
                      "meetings",
                      "teaching",
                      "sales",
                      "coaching",
                      "students",
                      "travel",
                      "chess",
                      "other"
                    ],
                    "description": "What the creator’s videos are about."
                  },
                  "samples": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 3,
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "maxLength": 300
                    },
                    "description": "Links to the creator’s videos on those platforms."
                  },
                  "country": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "Where most of the audience is."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Where the reply goes."
                  },
                  "followers": {
                    "type": [
                      "integer",
                      "string"
                    ],
                    "minimum": 0,
                    "maximum": 10000000000,
                    "maxLength": 20,
                    "description": "Optional: followers on the main account, in the creator’s own figure — a whole number, digits with thousands separators, or with k or m (12.5k)."
                  },
                  "typicalViews": {
                    "type": [
                      "integer",
                      "string"
                    ],
                    "minimum": 0,
                    "maximum": 10000000000,
                    "maxLength": 20,
                    "description": "Optional: the views a typical video gets, in the creator’s own figure, written as followers is."
                  },
                  "consent": {
                    "type": "boolean",
                    "const": true,
                    "description": "The applicant’s statement, kept with the application beside the date of the terms: “I’m 18 or older. I understand applying doesn’t mean I’ll be approved or paid, and only posts made after written approval can earn anything.”"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received; a person will reply to the address given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "The body was not JSON, or the named field is missing, too long or not a link on the listed platforms.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "body": {
                    "value": {
                      "error": "body"
                    }
                  },
                  "shape": {
                    "value": {
                      "error": "shape"
                    }
                  },
                  "name": {
                    "value": {
                      "error": "name"
                    }
                  },
                  "handles": {
                    "value": {
                      "error": "handles"
                    }
                  },
                  "niche": {
                    "value": {
                      "error": "niche"
                    }
                  },
                  "samples": {
                    "value": {
                      "error": "samples"
                    }
                  },
                  "country": {
                    "value": {
                      "error": "country"
                    }
                  },
                  "email": {
                    "value": {
                      "error": "email"
                    }
                  },
                  "followers": {
                    "value": {
                      "error": "followers"
                    }
                  },
                  "typicalViews": {
                    "value": {
                      "error": "typicalViews"
                    }
                  },
                  "consent": {
                    "value": {
                      "error": "consent"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Only POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "method": {
                    "value": {
                      "error": "method"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The program is closed to new applications.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "closed": {
                    "value": {
                      "error": "closed"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "An application for this address already arrived today, or too many forms came from one network in an hour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "fuse": {
                    "value": {
                      "error": "fuse"
                    }
                  },
                  "network": {
                    "value": {
                      "error": "network"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The application could not be recorded; write to the contact address instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "io": {
                    "value": {
                      "error": "io"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/creators/register": {
      "post": {
        "operationId": "registerCreatorPost",
        "tags": [
          "creators"
        ],
        "summary": "Register an approved creator’s post.",
        "description": "For approved creators: registers a public post so its verified views can be counted and paid under the program's rules — within 7 days of posting, with the code from the approval mail. A post is registered once; at most 10 posts a day for one code. An acknowledgement with the post's dates goes to the address given. A form post is answered with an HTML page instead of JSON.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "post",
                  "handle",
                  "code",
                  "posted",
                  "email",
                  "disclosure"
                ],
                "properties": {
                  "post": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 300,
                    "description": "The post’s link on TikTok, Instagram or YouTube."
                  },
                  "handle": {
                    "type": "string",
                    "pattern": "^@?[A-Za-z0-9._-]{1,30}$",
                    "description": "The account that posted it."
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^([a-z])-([a-z0-9](?:[a-z0-9-]{0,22}[a-z0-9]))\\.([A-Za-z0-9_-]{16})$",
                    "description": "The creator’s code, from the approval mail."
                  },
                  "posted": {
                    "type": "string",
                    "format": "date",
                    "description": "The day it went up (YYYY-MM-DD), within the last 7 days."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254,
                    "description": "Where the acknowledgement goes."
                  },
                  "disclosure": {
                    "type": "boolean",
                    "const": true,
                    "description": "The creator’s statement that the post is disclosed as paid, kept with the registration beside the date of the terms: “This post says it is paid: in the video, on screen and out loud; with the platform’s paid-partnership setting on; and with #ad at the start of its caption. I keep it up, with that disclosure, until it is paid.”"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered; the dates are on their way to the address given.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            }
          },
          "400": {
            "description": "The body was not JSON, or the named field is missing or wrong — a code the program did not issue is `code`, a day outside the window `posted`, the disclosure box unticked `disclosure`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "body": {
                    "value": {
                      "error": "body"
                    }
                  },
                  "shape": {
                    "value": {
                      "error": "shape"
                    }
                  },
                  "post": {
                    "value": {
                      "error": "post"
                    }
                  },
                  "handle": {
                    "value": {
                      "error": "handle"
                    }
                  },
                  "code": {
                    "value": {
                      "error": "code"
                    }
                  },
                  "posted": {
                    "value": {
                      "error": "posted"
                    }
                  },
                  "email": {
                    "value": {
                      "error": "email"
                    }
                  },
                  "disclosure": {
                    "value": {
                      "error": "disclosure"
                    }
                  }
                }
              }
            }
          },
          "405": {
            "description": "Only POST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "method": {
                    "value": {
                      "error": "method"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The post is already registered, or registrations are paused or closed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "duplicate": {
                    "value": {
                      "error": "duplicate"
                    }
                  },
                  "paused": {
                    "value": {
                      "error": "paused"
                    }
                  },
                  "closed": {
                    "value": {
                      "error": "closed"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Too many posts for this code today, or too many forms from one network in an hour.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "fuse": {
                    "value": {
                      "error": "fuse"
                    }
                  },
                  "network": {
                    "value": {
                      "error": "network"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "The registration could not be recorded; write to the contact address instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "io": {
                    "value": {
                      "error": "io"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "What was refused, as a short code."
          }
        }
      },
      "Status": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "True while the service is serving."
          }
        },
        "additionalProperties": true
      }
    }
  }
}
