> ## Documentation Index
> Fetch the complete documentation index at: https://docs.livepeer.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Livepeer Studio Changelog

> Release history for Livepeer Studio – video infrastructure APIs and dashboard.

export const CustomCardTitle = ({icon, title, variant = "card", iconSize, style = {}, className = "", ...rest}) => {
  const variants = {
    card: {
      display: 'flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)",
      marginBottom: "var(--lp-spacing-3)",
      color: 'var(--lp-color-text-primary)',
      fontSize: '1rem',
      fontWeight: 600
    },
    accordion: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: "var(--lp-spacing-2)"
    },
    tab: {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '0.4rem',
      fontSize: '0.875rem'
    }
  };
  const sizes = {
    card: 20,
    accordion: 18,
    tab: 14
  };
  const size = iconSize || sizes[variant] || 20;
  const baseStyle = variants[variant] || variants.card;
  return variant === 'card' ? <div className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </div> : <span className={className} style={{
    ...baseStyle,
    ...style
  }} {...rest}>
      {typeof icon === 'string' ? <Icon icon={icon} size={size} color="var(--lp-color-accent)" /> : icon}
      {title}
    </span>;
};

export const Subtitle = ({style = {}, text, children, variant = 'default', fontSize = '', fontWeight = '', fontStyle = '', marginTop = '', marginBottom = '', color = '', className = '', ...rest}) => {
  const renderInlineCode = (value, keyPrefix) => {
    return value.split(/(`[^`]+`)/g).map((segment, index) => {
      if (segment.startsWith('`') && segment.endsWith('`')) {
        return <code key={`${keyPrefix}-code-${index}`}>{segment.slice(1, -1)}</code>;
      }
      return segment;
    });
  };
  const renderInlineMarkup = (value, keyPrefix = 'subtitle') => {
    if (typeof value !== 'string') {
      return value;
    }
    return value.split(/(\*\*[\s\S]+?\*\*)/g).map((segment, index) => {
      if (segment.startsWith('**') && segment.endsWith('**')) {
        const inner = segment.slice(2, -2);
        return <strong key={`${keyPrefix}-strong-${index}`}>
            {renderInlineCode(inner, `${keyPrefix}-strong-${index}`)}
          </strong>;
      }
      return renderInlineCode(segment, `${keyPrefix}-${index}`);
    });
  };
  const renderContent = (value, keyPrefix) => {
    if (Array.isArray(value)) {
      return value.map((item, index) => renderContent(item, `${keyPrefix}-${index}`));
    }
    return renderInlineMarkup(value, keyPrefix);
  };
  const variants = {
    default: {
      fontSize: '1rem',
      fontStyle: 'italic',
      color: 'var(--lp-color-accent)',
      marginBottom: 0
    },
    changelog: {
      fontSize: '0.8rem',
      fontStyle: 'normal',
      fontWeight: 700,
      color: 'var(--lp-color-text-primary)',
      marginBottom: 0
    }
  };
  const base = variants[variant] || variants.default;
  return <span className={className} style={{
    ...base,
    ...fontSize ? {
      fontSize
    } : {},
    ...fontWeight ? {
      fontWeight
    } : {},
    ...fontStyle ? {
      fontStyle
    } : {},
    ...marginTop ? {
      marginTop
    } : {},
    ...marginBottom ? {
      marginBottom
    } : {},
    ...color ? {
      color
    } : {},
    ...style
  }} {...rest}>
      {renderContent(text, 'text')}
      {renderContent(children, 'children')}
    </span>;
};

export const BorderedBox = ({children, variant = "default", padding = "var(--lp-spacing-4)", borderRadius = "var(--lp-spacing-px-8)", margin = "", accentBar = "", style = {}, className = "", ...rest}) => {
  const variants = {
    default: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    accent: {
      border: "1px solid var(--lp-color-accent)",
      backgroundColor: "var(--lp-color-bg-card)"
    },
    muted: {
      border: "1px solid var(--lp-color-border-default)",
      backgroundColor: "transparent"
    }
  };
  const accentBarColors = {
    accent: "var(--lp-color-accent)",
    positive: "var(--green-9)"
  };
  return <div data-docs-bordered-box="" data-accent-bar={accentBarColors[accentBar] ? "" : undefined} className={className} style={{
    ...variants[variant],
    padding: padding,
    borderRadius: borderRadius,
    ...margin ? {
      margin
    } : {},
    ...accentBarColors[accentBar] ? {
      position: "relative",
      '--accent-bar-color': accentBarColors[accentBar]
    } : {},
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const LazyLoad = ({children, height = "200px", offset = "200px", fadeDuration = 400, className = "", style = {}, ...rest}) => {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  const [ready, setReady] = useState(false);
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const observer = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting) {
        setVisible(true);
        observer.disconnect();
      }
    }, {
      rootMargin: offset
    });
    observer.observe(el);
    return () => observer.disconnect();
  }, []);
  useEffect(() => {
    if (!visible) return;
    const frameId = requestAnimationFrame(() => {
      setReady(true);
    });
    return () => cancelAnimationFrame(frameId);
  }, [visible]);
  const placeholder = <div ref={ref} className={className} style={{
    minHeight: height,
    ...style
  }} {...rest} />;
  if (!visible) return placeholder;
  return <div ref={ref} className={className} style={{
    opacity: ready ? 1 : 0,
    transition: `opacity ${fadeDuration}ms ease-in`,
    ...style
  }} {...rest}>
      {children}
    </div>;
};

export const ScrollBox = ({children, maxHeight = 300, showHint = true, ariaLabel = "Scrollable content", style = {}, className = "", ...rest}) => {
  const contentRef = useRef(null);
  const [isOverflowing, setIsOverflowing] = useState(false);
  useEffect(() => {
    const checkOverflow = () => {
      if (contentRef.current) {
        const maxHeightPx = typeof maxHeight === "number" ? maxHeight : parseInt(maxHeight, 10) || 300;
        setIsOverflowing(contentRef.current.scrollHeight > maxHeightPx);
      }
    };
    checkOverflow();
    window.addEventListener("resize", checkOverflow);
    return () => window.removeEventListener("resize", checkOverflow);
  }, [maxHeight, children]);
  return <div className={className} style={{
    position: "relative",
    ...style
  }} {...rest}>
      <div ref={contentRef} role="region" tabIndex={0} aria-label={ariaLabel} style={{
    maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
    overflowY: "auto",
    paddingRight: 4
  }} onScroll={e => {
    const el = e.target;
    const atBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 10;
    const hint = el.parentNode.querySelector("[data-scroll-hint]");
    if (hint) hint.style.opacity = atBottom ? "0" : "1";
  }}>
        {children}
      </div>
      {showHint && isOverflowing && <div data-scroll-hint style={{
    fontSize: 11,
    color: "var(--lp-color-text-muted)",
    textAlign: "center",
    marginTop: 8,
    transition: "opacity 0.2s"
  }}>
          Scroll for more ↓
        </div>}
    </div>;
};

export const DoubleIconLink = ({label = '', labelColor, href = '#', text = '', iconLeft = 'github', iconLeftColor, iconRight = 'arrow-up-right', iconRightColor = 'var(--lp-color-accent)', className = '', style = {}, ...rest}) => {
  return <span className={className} style={{
    whiteSpace: 'nowrap',
    display: 'inline-flex',
    alignItems: 'center',
    gap: "var(--lp-spacing-1)",
    marginLeft: '0.3rem',
    ...style
  }} {...rest}>
      {text && <span style={{
    marginRight: 8
  }}>{text}</span>}
      <Icon icon={iconLeft} color={iconLeftColor} />
      <a href={href} style={{
    color: {
      labelColor
    }
  }}>
        {label}
      </a>
      <div style={{
    marginRight: '0.3rem'
  }}>
        <Icon icon={iconRight} size={12} color={iconRightColor} />
      </div>
    </span>;
};

export const LinkArrow = ({href, label, description, newline = true, borderColor, className = '', style = {}, ...rest}) => {
  const linkArrowStyle = {
    display: 'inline-flex',
    alignItems: 'center',
    justifyContent: 'center',
    gap: "var(--lp-spacing-1)",
    width: 'fit-content',
    ...borderColor && ({
      borderColor
    })
  };
  return <span className={className} style={style} {...rest}>
      {newline && <br />}
      <span style={linkArrowStyle}>
        <a href={href} target="_blank" rel="noopener noreferrer">
          {label}
        </a>
        <Icon icon="arrow-up-right" size={14} color="var(--lp-color-accent)" />
      </span>
      {description && description}
      {description && <div style={{
    height: "var(--lp-spacing-3)"
  }} />}
    </span>;
};

export const InlineDivider = ({margin = "0.75rem 0", padding = "0", color = "var(--lp-color-border-default)", opacity = 0.4, height = "1px", className = "", style = {}, ...rest}) => <hr role="separator" className={className} style={{
  border: "none",
  margin,
  padding,
  height,
  backgroundColor: color,
  opacity,
  ...style
}} {...rest} />;

export const CustomDivider = ({color = "var(--lp-color-border-default)", middleText = "", spacing = "default", style = {}, className = "", ...rest}) => {
  const spacingPresets = {
    default: {
      margin: "24px 0"
    },
    overlap: {
      margin: "-1rem 0 -1rem 0"
    },
    tight: {
      margin: "0 0 -1rem 0"
    },
    section: {
      margin: "0 0 -2rem 0"
    },
    sectionOverlap: {
      margin: "-1rem 0 -2rem 0"
    },
    deepOverlap: {
      margin: "-1rem 0 -1.5rem 0"
    }
  };
  const spacingStyle = spacingPresets[spacing] || spacingPresets.default;
  return <div role="separator" aria-orientation="horizontal" className={className} style={{
    display: "flex",
    alignItems: "center",
    ...spacingStyle,
    fontSize: style?.fontSize || "16px",
    height: "fit-content",
    ...style
  }} {...rest}>
      <span style={{
    marginRight: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
      </span>
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      {middleText && <>
          <Icon icon="circle" size={2} />
          <span style={{
    margin: "0 8px",
    fontWeight: "bold",
    color: color,
    opacity: 0.7
  }}>
            {middleText}
          </span>
          <Icon icon="circle" size={2} />
        </>}
      <div style={{
    flex: 1,
    height: "1px",
    background: "var(--lp-color-border-default)",
    opacity: 0.4
  }}></div>
      <span style={{
    marginLeft: "var(--lp-spacing-px-8)",
    opacity: 0.2
  }}>
        <span style={{
    display: "inline-block",
    transform: "scaleX(-1)"
  }}>
          <Icon icon="/snippets/assets/logos/Livepeer-Logo-Symbol-Theme.svg" />
        </span>
      </span>
    </div>;
};

export const playbackId_0 = undefined

<Tip>
  This page is an automated workflow.
  <Subtitle variant="changelog" style={{fontSize: "0.95rem", marginTop: "0.25rem"}}>Subscribe to this changelog's <LinkArrow label="RSS Feed" href="/v2/solutions/livepeer-studio/changelog/rss.xml" newline={false} /></Subtitle>
</Tip>

<CustomDivider style={{margin: "-0.5rem 0 -1.5rem 0"}} />

Track releases for <LinkArrow label="Livepeer Studio" href="https://github.com/livepeer/studio/releases" newline={false} /> on GitHub.

<Update label="v0.19.0" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.19.0", description: "* api: projects: allUsers param in projects list by @gioelecerati in https://github.com/livepeer/studio/pull/2206" }} description={<Subtitle variant="changelog">June 2024</Subtitle>}>
  ## v0.19.0

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * api: projects: allUsers param in projects list
    * api: Add profiles field to asset
    * api/jobs/update-usage: Process users in parallel and allow cancellation
    * fix: update old workspace deps
    * Tailwind and UI tweaks
    * Fix thumbs for direct uploads
    * api: Allow patching a stream recordingSpec
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.19.0" iconLeft="github" />
</Update>

<Update label="v0.18.0" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.18.0", description: "* api: access-control: allowedOrigins by @gioelecerati in https://github.com/livepeer/studio/pull/2176" }} description={<Subtitle variant="changelog">June 2024</Subtitle>}>
  ## v0.18.0

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * api: access-control: allowedOrigins
    * access-control: fix casing for origin header
    * access-control: use referer on redirects
    * access-control: handle referer decoding
    * add GTM
    * access-control: check empty origins
    * asset: payloa: remove project id from asset payload
    * access-control: tests: fix casing
    * Fix checking 'deleting' field while listing assets
    * api/task: Prevent tasks from suspended users from running
    * schema fixes
    * Change 'not-used-playback' to 'ingest-{playbackId_0}'
    * api: update asset's hash to include null type
    * api: stream: add cache to /stream/playback
    * "Restore" add projectId scoping to streams/sessions/webhook (#2103)
    * api: Livestream recording customisation
    * logger: Allow enabling verbose logs for staging
    * project: user: create default project id
    * api: Create entrypoints for long-running jobs
    * .github: Delete active-cleanup and billing cronjobs
    * api: billing: notify user disabled due to invalid payment method
    * api/stripe: Avoid sleeping too much when reporting usage
    * db: index: asset phase, deleted, deletedAt
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.18.0" iconLeft="github" />
</Update>

<Update label="v0.17.0" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.17.0", description: "* fix: fix broadcasting by @0xcadams in https://github.com/livepeer/studio/pull/2028" }} description={<Subtitle variant="changelog">May 2024</Subtitle>}>
  ## v0.17.0

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * fix: fix broadcasting
    * api: Add createdByTokenName field to assets
    * Add stream config endpoint
    * Set use replica false for stream config
    * update content on homepage
    * api: asset: limit on delete by user id
    * api: Implement MVP of stream pull trigger
    * Rename webhook response table
    * Remove unnecessary db field
    * Add webhook search field on request body
    * Add flv playback URL to playback info endpoint
    * Ability to set custom profiles for vod uploads
    * Ability to set custom segment size for vod uploads
    * access-control: added refresh interval for policies
    * Support for thumbs vtt opt out
    * tmp: rename rate limit
    * ci: add bucket upload of builds
    * hot fix: multistream state
    * fix: null safety in metrics lookup
    * docs: fix clip timestamp requirements
    * ui: new webhook page
    * api: webhook: allow additional headers & payload to be sent in webhooks
    * api: acl: bring limit for hackers down to 5 per node
    * acl: fix webhook headers
    * fix: update successful filter in webhooks logs
    * Add a webhook log success field to allow filtering in the UI
    * api: disable user on first payment failed
    * api: flag streams as pull created for auth header in webhook
    * api: better user disable on payment failed
    * acl: logs for webhook & header fix
    * Unsuspend stream with a new call to /pull
    * api: playback: require auth for playback recordings
    * add isHealthy to filters
    * Correct values for 'encoder' in Profiles
    * api: acl: remove redundant isPullStream
    * api: update param description
    * Terminate the stream if the pull source has been updated
    * api/stream/pull: Start pulls on the requested loc
    * stream/helpers: reduce some delays in await functions
    * Webhook page
    * Update customer template
    * api: Fix creatorId filter for assets
    * Always log response body for pull streams
    * Stream pull startup improvements
    * Pull stream start improvements
    * Allow for 200 access denied for webhook access control
    * Update faq.md
    * fix: moved upload via URL to separate API schema and added docs
    * Add admin fields to new upload payload definition
    * www/admin/stream: Add allow-same-origin to iframe
    * Create stream api fix
    * Call stream update for pull API rather than nuke
    * Revert "Call stream update for pull API rather than nuke (#2090)"
    * 0xcadams/create stream api fix
    * Projects in Studio
    * Call stop sessions rather than nuke for stream pull
    * Revert "Projects in Studio (#2078)"
    * Wait after sending stop sessions
    * Switch terminate to a stop sessions
    * Revert "Wait after sending stop sessions"
    * Remove stopsessions from /pull
    * Increase webhook timeout 5s => 30s
    * Delay the stream pull if it has been recently terminated
    * Return 429 for too many terminate calls
    * Add extra debug values to session re-use log
    * Switch from nuke to stop sessions when disabling users
    * access-control: fix status code of gate
    * Implement locking stream pull to avoid multiple catalyst pulling the …
    * projects: add projects and projectId scoping to assets/api-token cotrollers
    * access-control.test: fix test to receive correct error
    * project: return project object for CRUD api
    * Revert "Implement locking stream pull to avoid multiple catalyst pull lock
    * Implement locking stream pull to avoid multiple catalyst pulling the stream
    * Add host param to lockPull endpoint
    * lockPull: check isActive in the transaction and only by different nodes
    * fix: remove encodings from API schema
    * Release pull lock when stream is terminated
    * api: Enable local IP verification on webhooks
    * api: Attempt processing recording up to 5 times
    * Revert "api: Enable local IP verification on webhooks (#2118)"
    * api/store: Handle connection failure with RabbitMQ
    * remove mention of “free” from recording tooltip
    * api: webhook: log headers
    * api: webhook: fix header log
    * Add pullRegion to streams to make the ingest node sticky
    * api: gate: add playurl to logs
    * Integrating June tracking tool
    * fix: fix June method
    * api: asset: delete & restore
    * Add additional logs to /pull
    * feat: add stream filters
    * ui: fix the stream states
    * Stick to the pull host while triggering the catalyst pull start
    * Revert "Stick to the pull host while triggering the catalyst pull start"
    * Stick to the pull host while triggering the catalyst pull start
    * api: ingest: direct base & playback
    * api: Handle active cleanup from pull lock API
    * refactor: improve api schema
    * api: Query isHealthy field as a string to handle JSOnull
    * api: moved objectStoreId and catalystPipelineStrategy to db schema
    * api: fix direct playback api
    * fix june trigger
    * fix stream page ui issues
    * api: Log the full pull payload for debugging
    * add canny redirect
    * api: stream: test profiles for test creator ids
    * api/stream: Allow admins to use API key to patch stream
    * api: stream: added isMobile to pull streams
    * api/stream: Add a couple more hacks for testing Trovo profiles
    * move auth routes to /dashboard
    * reskin auth pages to match new site
    * update email button urls
    * remove corporate site, move everything to /dashboard
    * add redirect to dashboard
    * .github: Create staging active streams cleanup job
    * .github: Improve active-cleanup action
    * api/stream: Make sure we use patched payload on /pull create stream
    * api: Create logic for automatically cleaning up dead streams
    * api,www: Create `failed` state for recordings
    * api/stream: Order streams by lastSeen on active-cleanup
    * api/cannon: Re-fix local IP check with better error handling
    * api: Fix double index creation with separate jobs DB pool
    * Reuse catalyst stream pull node if pull is locked
    * api: Fix the hack for non-standard profiles on pull API
    * api: Allow isMobile to be specified as an int (0 or 1... Or 2!)
    * api/stream: Only clear fps if isMobile === 2
    * add projectId scoping to streams/sessions/webhook
    * api/cannon: Stop retrying invalid webhooks forever
    * Revert "add projectId scoping to streams/sessions/webhook (#2103)"
    * fix: change to nullable create/patch stream
    * refactor: improve api schema
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.17.0" iconLeft="github" />
</Update>

<Update label="v0.16.2" tags={["Release"]} rss={{ title: "Livepeer Studio v0.16.2", description: "* api: Create PUT /stream/pull API for idempotent pull stream by @victorges in https://github.com/livepeer/studio/pul..." }} description={<Subtitle variant="changelog">February 2024</Subtitle>}>
  ## v0.16.2

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * api: Create PUT /stream/pull API for idempotent pull stream
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.16.2" iconLeft="github" />
</Update>

<Update label="v0.16.1" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.16.1", description: "* Document the quality param by @leszko in https://github.com/livepeer/studio/pull/1996" }} description={<Subtitle variant="changelog">February 2024</Subtitle>}>
  ## v0.16.1

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * Document the quality param
    * api: Fix index usage for usage queries
    * api: Stop spamming external Object Stores
    * feat: added login and update livepeer.js
    * Revert "feat: added login and update livepeer.js"
    * Return targetId while creating a multistream target
    * clipping: enforce endTime > startTime
    * refactor: update security schema
    * feat: integrate ripe for tracking leads
    * ui: fix the pricing grid
    * www: Implement JWT refreshing logic
    * don't produce db query related prometheus metrics
    * restore dropped comment
    * user: patch api & change email
    * www: Fix initial API client state
    * Start on webhook log API
    * feat: added login and update livepeer.js
    * Rename /requests to /log
    * access-control: move rate limit to catalyst
    * api: added user custom tags to stream object
    * api: Pull ingest for streams
    * api: Fix ensureExperimentSubject call
    * update the content on homepage
    * fix: bump livepeer react
    * ui: add status page link
    * api: fix userTags type
    * Webhook get and resend APIs

    ## New Contributors

    * @pwilczynskiclearcode made their first contribution in [https://github.com/livepeer/studio/pull/2014](https://github.com/livepeer/studio/pull/2014)
    * @denbrkic made their first contribution in [https://github.com/livepeer/studio/pull/2023](https://github.com/livepeer/studio/pull/2023)
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.16.1" iconLeft="github" />
</Update>

<Update label="v0.16.0" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.16.0", description: "* Add Quality param to profile by @leszko in https://github.com/livepeer/studio/pull/1880" }} description={<Subtitle variant="changelog">December 2023</Subtitle>}>
  ## v0.16.0

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * Add Quality param to profile
    * Add quality param for livestreams
    * clip: fix api failing when unauthenticated requests come in
    * Increase publishTimeout to 60s
    * fix: typo
    * db: cache objects with ttl
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.16.0" iconLeft="github" />
</Update>

<Update label="v0.15.7" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.15.7", description: "* Handle update task failure by @mjh1 in https://github.com/livepeer/studio/pull/1983" }} description={<Subtitle variant="changelog">December 2023</Subtitle>}>
  ## v0.15.7

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * Handle update task failure
    * api/schema: Add docs on stream terminate API
    * refactor: fixed openapi errors (api-schema)
    * chore: add dispatch event workflow
    * multistream: atomic add and remove targets from a given stream
    * Revert "add vercel analytics (#1976)"
    * access-control: admin public key access
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.15.7" iconLeft="github" />
</Update>

<Update label="v0.15.6" tags={["Features", "Fixes"]} rss={{ title: "Livepeer Studio v0.15.6", description: "* api: have tus respect X-Forwarded-Proto headers by @iameli in https://github.com/livepeer/studio/pull/1874" }} description={<Subtitle variant="changelog">November 2023</Subtitle>}>
  ## v0.15.6

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * api: have tus respect X-Forwarded-Proto headers
    * api: attestation test was missing an await for clearDatabase
    * billing: remove migration endpoints
    * Add support for a failover recording bucket
    * webhook: b64 encode the response of failing webhooks
    * fix: remove redirect
    * fix: changed search to case insensitive
    * www: billing: remove upcoming invoice total on dashboard home page
    * Update site
    * fix prettier
    * Fix dark mode in dashboard
    * Fix compare page
    * chore: bump lvpr js
    * Update compare heading
    * Implement vod storage experiment/feature toggle
    * Remove transcode and import tasks
    * api: user: added email verification messages
    * clip: define clip api
    * api: delete user api
    * playback: improve dvr playback error handling
    * session: don't fail when no sessions exist
    * clip: allow sdk clipping
    * api: allow overriding default stream profiles
    * clip: livepeerjs clipping & admin check
    * clip: fix clipping permissions
    * api: bump tus server to 1.0.0
    * api: get clips
    * user: fix delete user authorizer
    * clip: show clips in the dashboard
    * Increase final retry delay
    * api: clip: get clips api
    * clip: api: fix get clips by stream
    * www: clips: display session ids also in recordings
    * Support secondary private object store
    * Fix room delete error
    * stream: fix idle state race condition
    * fix: z index on sidebar
    * clip: static mp4 as downloadUrl
    * clip: remove stream.record check from clip & playback info
    * clip: clip by session id
    * api: clips rate limit
    * clip: change processing query
    * clip: fix rate limiting
    * clip: fix headers in clip api
    * api: abstract requesterId
    * api: Nuke streams asynchronously w/ events
    * Request VOD thumbnails based on user experiment
    * Don't log headers
    * Log database queries at debug level
    * Add new pages: product, customer, blog
    * Enable secondary storage by default with a blocklist via an experiment
    * billing: usage notifications
    * api: fix resend validation email
    * Add C2PA support
    * Return live thumbnail URL from playback API
    * api: user: added disabled field
    * Add secondary store logic to recordings
    * user: disable on tier limit & enable enforcement
    * usage: fix usage query
    * usage: fetch active users from analyzer
    * usage: report: fix recent active
    * usage: added notification logs
    * notifications: better usage notifications
    * access-control: enforce viewers limit for free tier
    * Revert "Return live thumbnail URL from playback API (#1938)"
    * notifications: fix email sent check
    * billing: patch subscription endpoint
    * gate: enforce viewers limit in memory
    * Revert "stream: fix idle state race condition (#1916)"
    * www: fix active right now status
    * Return live thumbnail URL from playback API
    * fix: login redirect
    * Add /heartbeat endpoint
    * api: asset: delete assets by user
    * api: clip: added clip apis reference to api-schema
    * usage: enable hourly reporting
    * Support patching profiles in a stream
    * Fix Stream Idle by using the isActive from webhook, not stream
    * Revert "Fix Stream Idle by using the isActive from webhook, not strea…
    * Update faq.md
    * api: filter streams by playbackId
    * stream: fix idle state race condition (#1916)
    * Yarn prettier
    * api: asset: delete multiple asset at once fixes
    * Return live thumbnail URL for everyone
    * Return VOD thumbs VTT URL when available
    * Fix removing Multistream Target from UI
    * docs: added better docs to api schema
    * add vercel analytics
    * reduce revalidate seconds for pages
    * api: asset: rerun tasks
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.15.6" iconLeft="github" />
</Update>

<Update label="v0.15.5" tags={["Fixes"]} rss={{ title: "Livepeer Studio v0.15.5", description: "* billing: hackers with pay as you go by @gioelecerati in https://github.com/livepeer/studio/pull/1867" }} description={<Subtitle variant="changelog">September 2023</Subtitle>}>
  ## v0.15.5

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * billing: hackers with pay as you go
    * www: plans: fix alert text
    * package.json: i guess don't use lerna to run tests?
    * test: increase timeout
    * fix box www fallback
    * www: gitignore sitemap
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.15.5" iconLeft="github" />
</Update>

<Update label="v0.15.4" tags={["Release"]} rss={{ title: "Livepeer Studio v0.15.4", description: "* chore: bump @livepeer/react@^2.8.2 by @iameli in https://github.com/livepeer/studio/pull/1864" }} description={<Subtitle variant="changelog">September 2023</Subtitle>}>
  ## v0.15.4

  <ScrollBox maxHeight="250px" showHint={true}>
    ## What's Changed

    * chore: bump @livepeer/react@^2.8.2
  </ScrollBox>

  <DoubleIconLink label="View release on GitHub" href="https://github.com/livepeer/studio/releases/tag/v0.15.4" iconLeft="github" />
</Update>
