openapi: 3.1.0
info:
  title: Go Live Plus Developer API
  version: "1.0.0"
  summary: 'Push comments from your platform into a Go Live Plus live; take the live and what the AI host says to your platform.'
  description: |
    The live itself runs on the seller's own computer (the Go Live Plus app). This API lives in the
    cloud and relays to that computer over a connection the app opens, so events reach the host only
    while the session is live or rehearsing in Studio with the app signed in.

    Latency: typically well under a second from your POST to the engine's comment queue; the host's
    spoken answer then takes as long as any answer (knowledge base or language model, then voice).

    Viewer text is always treated as untrusted viewer content: the same triage, blocklist, per-minute
    reply caps and answer guardrails apply as for TikTok or YouTube comments.
  contact:
    url: https://v3.aseacast.com/developers
servers:
  - url: https://v3.aseacast.com/api/v1
security:
  - apiKey: []
tags:
  - name: Account
  - name: Sessions
  - name: Events
  - name: Host events
  - name: Outputs
  - name: Webhooks
paths:
  /me:
    get:
      tags: [Account]
      summary: 'The key''s team, plan, scopes and limits'
      responses:
        "200": { description: OK, content: { application/json: { schema: { $ref: "#/components/schemas/Me" } } } }
        default: { $ref: "#/components/responses/Error" }
  /rooms:
    get:
      tags: [Sessions]
      summary: 'Rooms and their Live Sessions'
      description: 'Needs `sessions:read`.'
      responses:
        "200":
          description: 'OK'
          content:
            application/json:
              schema:
                type: object
                properties:
                  rooms:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        name: { type: string }
                        preset: { type: string }
                        sessions: { type: array, items: { type: object, properties: { id: { type: string }, name: { type: string } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions:
    get:
      tags: [Sessions]
      summary: 'Every Live Session with its live status'
      description: 'Needs `sessions:read`.'
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { sessions: { type: array, items: { $ref: "#/components/schemas/Session" } } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}:
    parameters: [{ $ref: "#/components/parameters/SessionId" }]
    get:
      tags: [Sessions]
      summary: 'One session''s status (live / rehearsal / offline), computer and hosts'
      description: 'Needs `sessions:read`. `current` names the team''s live on air now (409 `session_offline` if none).'
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { session: { $ref: "#/components/schemas/Session" } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/events:
    parameters: [{ $ref: "#/components/parameters/SessionId" }]
    post:
      tags: [Events]
      summary: 'Push comments and events into the live'
      description: |
        Needs `events:write`. One event, or a batch of up to 50 sharing a `platform`. All or nothing:
        one invalid event refuses the batch with its index. An event `id` is de-duplicated for 15 minutes;
        `Idempotency-Key` makes a retried request return the first answer (24 h).
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string, maxLength: 100, pattern: "^[\\w.:-]+$" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/Event"
                - $ref: "#/components/schemas/EventBatch"
            examples:
              comment:
                value: { id: "c-1001", platform: { id: "myshop", name: "MyShop" }, user: { id: "u42", name: "Aina" }, text: "Berapa harga saiz L?" }
              batch:
                value: { platform: { id: "myshop", name: "MyShop" }, events: [ { kind: "join", user: { name: "Ali" } }, { kind: "gift", user: { name: "Siti" }, meta: { giftName: "Rose", giftValue: 10 } } ] }
      responses:
        "202":
          description: 'Queued for the computer running the live'
          headers:
            Idempotent-Replayed: { schema: { type: string }, description: "true when this answer repeats an earlier request" }
          content:
            application/json:
              schema:
                type: object
                properties:
                  sessionId: { type: string }
                  status: { type: string, enum: [live, rehearsal] }
                  queued: { type: integer }
                  duplicates: { type: integer }
                  events: { type: array, items: { type: object, properties: { id: { type: string }, status: { type: string, enum: [queued, duplicate] } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/stream:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - { name: token, in: query, required: false, schema: { type: string }, description: "Stream token instead of the API key (browsers)" }
      - { name: after, in: query, required: false, schema: { type: integer }, description: "Resume after this event id (or send Last-Event-ID)" }
    get:
      tags: [Host events]
      summary: 'Server-Sent Events of what the host says and does'
      description: 'Needs `stream:read` (or a stream token). Opens with a `ready` event; `: ping` every 15 s; closes after 30 min (EventSource reconnects).'
      security: [{ apiKey: [] }, { streamToken: [] }]
      responses:
        "200": { description: "text/event-stream; each event's data is a HostEvent", content: { text/event-stream: { schema: { type: string } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/host-events:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - { name: after, in: query, schema: { type: integer } }
      - { name: token, in: query, schema: { type: string } }
    get:
      tags: [Host events]
      summary: 'The same events as JSON (catch up / poll); last 500, two hours'
      security: [{ apiKey: [] }, { streamToken: [] }]
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { sessionId: { type: string }, head: { type: integer }, events: { type: array, items: { $ref: "#/components/schemas/HostEvent" } } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/stream-tokens:
    parameters: [{ $ref: "#/components/parameters/SessionId" }]
    post:
      tags: [Host events]
      summary: 'A read-only token (max 1 h) for one session''s stream, for a browser'
      requestBody: { content: { application/json: { schema: { type: object, properties: { ttlSeconds: { type: integer, minimum: 60, maximum: 3600 } } } } } }
      responses:
        "201": { description: Created, content: { application/json: { schema: { type: object, properties: { token: { type: string }, expiresAt: { type: string, format: date-time }, sessionId: { type: string }, streamUrl: { type: string } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/outputs:
    parameters: [{ $ref: "#/components/parameters/SessionId" }]
    get:
      tags: [Outputs]
      summary: 'Custom RTMP(S) outputs (server host only — URL and key are never returned)'
      description: 'Needs `outputs:manage`.'
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { outputs: { type: array, items: { $ref: "#/components/schemas/Output" } } } } } } }
        default: { $ref: "#/components/responses/Error" }
    post:
      tags: [Outputs]
      summary: 'Register an RTMP(S) ingest the seller''s computer pushes the live to at go-live'
      description: 'Needs `outputs:manage`. Up to 2 per session. Each takes one platform slot of the plan and one encoder on the seller''s computer.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label, url]
              properties:
                label: { type: string, maxLength: 40 }
                url: { type: string, description: "rtmp:// or rtmps:// server URL" }
                streamKey: { type: string, maxLength: 512 }
                enabled: { type: boolean, default: true }
      responses:
        "201": { description: Created, content: { application/json: { schema: { type: object, properties: { output: { $ref: "#/components/schemas/Output" } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /sessions/{id}/outputs/{outputId}:
    parameters:
      - $ref: "#/components/parameters/SessionId"
      - { name: outputId, in: path, required: true, schema: { type: string } }
    patch:
      tags: [Outputs]
      summary: 'Enable or disable'
      requestBody: { required: true, content: { application/json: { schema: { type: object, required: [enabled], properties: { enabled: { type: boolean } } } } } }
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
    delete:
      tags: [Outputs]
      summary: 'Delete (URL and key are erased)'
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
  /webhooks:
    get:
      tags: [Webhooks]
      summary: 'List'
      responses:
        "200": { description: OK, content: { application/json: { schema: { type: object, properties: { webhooks: { type: array, items: { $ref: "#/components/schemas/Webhook" } } } } } } }
        default: { $ref: "#/components/responses/Error" }
    post:
      tags: [Webhooks]
      summary: 'Add a webhook (https, public address). The signing secret is returned once.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string }
                events: { type: array, items: { type: string }, description: 'Host event types, or ["*"]' }
                sessionIds: { type: array, items: { type: string }, description: Empty = every session }
                description: { type: string, maxLength: 120 }
      responses:
        "201": { description: Created, content: { application/json: { schema: { type: object, properties: { webhook: { $ref: "#/components/schemas/Webhook" }, secret: { type: string } } } } } }
        default: { $ref: "#/components/responses/Error" }
  /webhooks/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
    get:
      tags: [Webhooks]
      summary: 'The webhook and its last 20 delivery attempts'
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
    patch:
      tags: [Webhooks]
      summary: 'Pause or resume'
      requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { active: { type: boolean } } } } } }
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
    delete:
      tags: [Webhooks]
      summary: 'Delete'
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
  /webhooks/{id}/test:
    parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
    post:
      tags: [Webhooks]
      summary: 'Send one signed webhook.test event now'
      responses:
        "200": { description: OK }
        default: { $ref: "#/components/responses/Error" }
webhooks:
  hostEvents:
    post:
      summary: 'A batch of host events'
      description: |
        Header `GLP-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`; also
        `GLP-Webhook-Id`, `GLP-Delivery-Id`, `GLP-Attempt`. Answer 2xx within 5 s. Retried after
        10 s, 1 min, 5 min and 30 min; the delivery id stays the same.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                id: { type: string, description: Delivery id }
                createdAt: { type: string, format: date-time }
                events: { type: array, items: { $ref: "#/components/schemas/HostEvent" } }
      responses:
        "200": { description: Received }
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "A key from Dashboard → Developer / API (glpk_…). Keep it on your server."
    streamToken:
      type: apiKey
      in: query
      name: token
  parameters:
    SessionId:
      name: id
      in: path
      required: true
      schema: { type: string }
      description: 'A Live Session id, or `current` for the team''s live on air now'
  responses:
    Error:
      description: 'Error'
      headers:
        Retry-After: { schema: { type: integer }, description: On 429 }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              enum: [unauthorized, forbidden, insufficient_scope, plan_required, not_found, invalid_request, payload_too_large, unsupported_media_type, rate_limited, session_offline, conflict, limit_reached, internal]
            message: { type: string }
    Me:
      type: object
      properties:
        team: { type: object, properties: { id: { type: string }, name: { type: string } } }
        plan: { type: string }
        key: { type: object, properties: { name: { type: string }, prefix: { type: string }, scopes: { type: array, items: { type: string } } } }
        limits: { type: object, additionalProperties: { type: integer } }
    Session:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        room: { type: object, properties: { id: { type: string }, name: { type: string }, preset: { type: string } } }
        status: { type: string, enum: [live, rehearsal, offline] }
        acceptsEvents: { type: boolean, description: The app is connected and will receive events }
        run: { type: [object, "null"], properties: { id: { type: string }, startedAt: { type: [string, "null"] } } }
        computer: { type: [object, "null"], properties: { id: { type: string }, name: { type: string } } }
        hosts: { type: array, items: { type: object, properties: { id: { type: string }, name: { type: string } } } }
    Event:
      type: object
      required: [user]
      properties:
        id: { type: string, maxLength: 100, description: Your id; repeats within 15 min are ignored }
        kind: { type: string, enum: [comment, question, join, like, follow, share, gift, subscribe, purchase, purchase_intent], default: comment }
        platform:
          oneOf:
            - { type: string }
            - { type: object, required: [id], properties: { id: { type: string }, name: { type: string } } }
          description: 'Your platform, shown in Studio''s comment feed (id is slugged; name ≤ 24 chars)'
        user: { type: object, required: [name], properties: { id: { type: string, maxLength: 128 }, name: { type: string, maxLength: 64 } } }
        text: { type: string, maxLength: 500, description: Required for comment and question }
        hostId: { type: string, description: In a multi-host room, the host who should answer }
        productId: { type: string, description: The product the comment is about (routing / answers) }
        meta:
          type: object
          properties:
            giftName: { type: string }
            giftValue: { type: number }
            count: { type: integer }
            likeCount: { type: integer }
            likeTotal: { type: integer }
            productId: { type: string }
            productName: { type: string }
            amount: { type: number }
            currency: { type: string }
            quantity: { type: integer }
        at: { type: string, format: date-time }
    EventBatch:
      type: object
      required: [events]
      properties:
        platform: { $ref: "#/components/schemas/Event/properties/platform" }
        events: { type: array, maxItems: 50, items: { $ref: "#/components/schemas/Event" } }
    HostEvent:
      type: object
      properties:
        id: { type: string, description: Increasing per session; use as Last-Event-ID }
        type: { type: string, enum: [speech.start, speech.end, product.shown, product.hidden, session.started, session.live, session.ended, event.result, webhook.test] }
        sessionId: { type: string }
        runId: { type: [string, "null"] }
        at: { type: string, format: date-time }
        data:
          type: object
          description: |
            speech.start: {utteranceId, text, hostId, hostName, kind: answer|greeting|script|line, replyTo?: {eventId?, platform, user, text}}
            speech.end: {utteranceId, hostId}
            product.shown: {productId, name, price}; product.hidden: {productId}
            session.started: {mode}; session.live, session.ended: {}
            event.result: {eventId, status: accepted|duplicate|blocked|invalid|rate_limited|denied}
    Output:
      type: object
      properties:
        id: { type: string }
        sessionId: { type: string }
        label: { type: string }
        server: { type: string, description: "scheme://host:port only" }
        streamKeySet: { type: boolean }
        enabled: { type: boolean }
        lastUsedAt: { type: [string, "null"] }
    Webhook:
      type: object
      properties:
        id: { type: string }
        url: { type: string }
        events: { type: array, items: { type: string } }
        sessionIds: { type: array, items: { type: string } }
        active: { type: boolean }
        failureCount: { type: integer }
        lastStatus: { type: [integer, "null"] }
        lastError: { type: [string, "null"] }
        lastDeliveryAt: { type: [string, "null"] }
