Avatar handle with a hyphen is rejected: the valid pattern

Sume avatar handles allow letters, digits, periods and underscores, 2 to 30 characters. A hyphen fails the pattern. Check yours in Python first.

4 min readSume
All posts

An avatar handle such as product-host does not match the pattern in the Sume OpenAPI: only letters, digits, periods and underscores are allowed, 2 to 30 characters, and a period or underscore cannot be first, last or doubled. Use product_host instead. A leading @ is optional: Sume lowercases the handle and stores it without the @.

What the pattern accepts

The Avatar 1.0 create route (POST /v1/avatar-1.0/generate) takes a top-level avatar_handle. The OpenAPI pattern for face swap and TTS is the same grammar: ^@?(?![._])(?!.*[._]$)(?!.*[._]{2})[A-Za-z0-9._]{2,30}$. The table runs ten candidates through that regex.

Handle candidates checked against the OpenAPI pattern (read 2026-10-09)
CandidateResultReason or stored form
product_hostacceptedstored as product_host
product-hostrejectedhyphen is not in the allowed set
@Product.Hostacceptedstored as product.host
brand..hostrejectedconsecutive periods
.hostrejectedstarts with a period or underscore
host_rejectedends with a period or underscore
arejectedshorter than 2 characters
sume_clawraacceptedstored as sume_clawra
host.v2acceptedstored as host.v2
xxxxxx... (31 x's)rejectedlonger than 30 characters

Check handles before you submit

Add a local check in your import script so a bad handle fails in your code and not in a batch of 200 creates. The pattern is the one from the docs; keep it in sync if the OpenAPI changes.

import re

HANDLE = re.compile(r"^@?(?![._])(?!.*[._]$)(?!.*[._]{2})[A-Za-z0-9._]{2,30}$")

def to_handle(name: str) -> str:
    """Turn a brand name into a legal handle, or raise."""
    cand = re.sub(r"[^A-Za-z0-9._]+", "_", name.strip()).strip("._")
    cand = re.sub(r"[._]{2,}", "_", cand)
    if not HANDLE.match(cand):
        raise ValueError(f"no legal handle for {name!r}")
    return cand.lower()

print(to_handle("Product Host"))    # product_host
print(to_handle("brand-host.v2"))  # brand_host.v2

Limits and gotchas

  • The handle is the name you reuse later in avatar_handle on talking videos, so pick one you will not want to change.
  • Handles must be normalized by you if you compare them: @Product.Host and product.host are the same avatar.
  • The same grammar applies when you pass avatar_handle to TTS or face swap, so one validator covers all three.
  • Creating the avatar is a fixed per-avatar price, listed in GET /v1/catalog.

Sources

Related posts

More in Sume Avatar 1.0

All Sume Avatar 1.0 posts

Written by Sume