
Ticket Availability
- 1.8k installs
- 7k repo stars
- Updated August 2, 2026
- nomadamas/k-skill
ticket-availability is an agent skill for yes24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 http 호출로 조회 (조회 전용, 예매·결제 없음).
About
The ticket-availability skill is designed for yES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음). Ticket Availability What this skill does YES24 (ticket.yes24.com) 와 인터파크 (tickets.interpark.com) 의 공개 BFF JSON / Ajax endpoint 를 단일 HTTP 요청으로 호출해 공연 일정과 등급별 잔여석 수를 정규화한다. CloakBrowser, Playwright, fingerprint spoofing, CAPTCHA 우회를 사용하지 않는다 (httpx only). Invoke when the user asks about ticket availability or related SKILL.md workflows.
- 공연 URL 또는 platform:id 표기로 입력을 받는다.
- 일정 (날짜·시간·회차) 조회.
- 등급별 잔여석 수 조회 (등급명, 잔여수, YES24의 경우 노출가).
- 좌석맵 / 좌석 선택 / 예매 / 결제 / 로그인 세션 접근은 하지 않는다.
- CloakBrowser, Playwright, fingerprint spoofing, CAPTCHA 우회를 사용하지 않는다 (httpx only).
Ticket Availability by the numbers
- 1,848 all-time installs (skills.sh)
- +233 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #411 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
ticket-availability capabilities & compatibility
- Capabilities
- 공연 url 또는 platform:id 표기로 입력을 받는다 · 일정 (날짜·시간·회차) 조회 · 등급별 잔여석 수 조회 (등급명, 잔여수, yes24의 경우 노출가) · 좌석맵 / 좌석 선택 / 예매 / 결제 / 로그인 세션 접근은 하지 않는다
- Use cases
- testing
What ticket-availability says it does
YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음).
YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음).
npx skills add https://github.com/nomadamas/k-skill --skill ticket-availabilityAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.8k |
|---|---|
| repo stars | ★ 7k |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 2, 2026 |
| Repository | nomadamas/k-skill ↗ |
How do I yes24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 http 호출로 조회 (조회 전용, 예매·결제 없음)?
YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음).
Who is it for?
Developers using ticket availability workflows documented in SKILL.md.
Skip if: Skip when the task falls outside ticket-availability scope or needs a different stack.
When should I use this skill?
User asks about ticket availability or related SKILL.md workflows.
What you get
Completed ticket-availability workflow with documented commands, files, and expected deliverables.
- JSON schedule output
- JSON seat availability counts
By the numbers
- Supports 2 ticketing platforms: YES24 and Interpark
- Exposes 3 CLI commands: schedule, seats, and health
Files
Ticket Availability
What this skill does
YES24 (ticket.yes24.com) 와 인터파크 (tickets.interpark.com) 의 공개 BFF JSON / Ajax endpoint 를 단일 HTTP 요청으로 호출해 공연 일정과 등급별 잔여석 수를 정규화한다.
- 공연 URL 또는
platform:id표기로 입력을 받는다. - 일정 (날짜·시간·회차) 조회.
- 등급별 잔여석 수 조회 (등급명, 잔여수, YES24의 경우 노출가).
- 좌석맵 / 좌석 선택 / 예매 / 결제 / 로그인 세션 접근은 하지 않는다.
- CloakBrowser, Playwright, fingerprint spoofing, CAPTCHA 우회를 사용하지 않는다 (
httpxonly).
When to use
- "오늘 인터파크 ○○ 공연 잔여석 있어?"
- "YES24 콘서트 ID 58026 일정 알려줘"
- "이 공연 R석 몇 자리 남았어?"
- "공연 URL 줄게, 회차별 잔여석 확인해줘"
When not to use
- 예매·결제·취소·환불 처리 — 공연법 §4조의2 (2023.9 시행) 매크로 입장권 부정구매·판매 금지 대상이며 이 스킬은 의도적으로 예매를 지원하지 않는다.
- 좌석 선택, 좌석맵 시각화, 특정 좌석 번호 확인 — 잔여 수 만 노출한다.
- 회원 등급별 우선 예매, 쿠폰가, 카드사 할인가 — 공개 endpoint 만 사용한다.
- 차단 우회, CAPTCHA 우회, headless 감지 우회 —
httpx한 호출로 안 되면 실패 모드로 처리하고 종료한다.
Required inputs
공연 URL 또는 platform:id 표기가 없으면 먼저 물어본다.
권장 질문:
확인하실 공연의 YES24 또는 인터파크 URL을 알려주세요.
예: https://tickets.interpark.com/goods/26000541 https://ticket.yes24.com/Perf/58026Prerequisites
- Python 3.9+
httpx(표준 패키지)
설치:
pip install httpxWorkflow
1. URL 파싱
| 입력 | 매칭 |
|---|---|
https://tickets.interpark.com/goods/<goods_code> | platform=interpark |
https://ticket.yes24.com/Perf/<perf_id> | platform=yes24 |
https://ticket.yes24.com/New/Perf/Detail/View/<perf_id> | platform=yes24 |
yes24:<id> / interpark:<id> | shorthand |
2. 일정 조회 (schedule)
python3 scripts/ticket_availability.py schedule "https://tickets.interpark.com/goods/26000541"응답 — Interpark:
{
"platform": "interpark",
"id": "26000541",
"schedule": [
{"date": "2026-05-13", "time": "14:30", "play_seq": "055"},
{"date": "2026-05-14", "time": "19:30", "play_seq": "057"}
]
}응답 — YES24:
{
"platform": "yes24",
"id": "58026",
"schedule": [
{"date": "2026-05-16", "time_label": "1회", "id_time": "1432397"}
]
}YES24 는 기본 3주 윈도우. 6개월 전체는 --all-dates 추가.
3. 잔여석 조회 (seats)
python3 scripts/ticket_availability.py seats "interpark:26000541"응답:
{
"platform": "interpark",
"id": "26000541",
"seats": {
"2026-05-13|14:30|055": {
"date": "2026-05-13", "time": "14:30", "play_seq": "055",
"seats": [
{"grade": "VIP석", "remain": 150},
{"grade": "R석", "remain": 36},
{"grade": "S석", "remain": 82},
{"grade": "A석", "remain": 71}
]
}
}
}YES24 응답은 등급별 price (노출가) 도 포함:
{"grade": "전석", "price": "110,000원", "remain": 2}4. 헬스체크 (health)
python3 scripts/ticket_availability.py health응답:
{"yes24": {"status": 200, "ok": true}, "interpark": {"status": 200, "ok": true}}Output format
기본 출력은 들여쓰기 JSON. 파이프/스크립트용은 --compact 추가 (한 줄 JSON).
Endpoints used
이 스킬이 호출하는 공개 endpoint 만:
| Platform | Method | URL |
|---|---|---|
| YES24 | POST | https://ticket.yes24.com/New/Perf/Sale/Ajax/axPerfDay.aspx |
| YES24 | POST | https://ticket.yes24.com/NEw/Perf/Detail/Ajax/axPerfPlayTime.aspx |
| YES24 | POST | https://ticket.yes24.com/New/Perf/Detail/Ajax/axPerfRemainSeat.aspx |
| Interpark | GET | https://api-ticketfront.interpark.com/v1/goods/<id>/playSeq |
| Interpark | GET | https://api-ticketfront.interpark.com/v1/goods/<id>/playSeq/PlaySeq/<seq>/REMAINSEAT |
전부 비로그인 / 무인증. 헤더는 User-Agent + Referer + JSON Accept 만.
Failure modes
- YES24 `schedule` 결과 빈 배열: 공연 ID 가 유효하지만 향후 3주(또는 6개월) 내 일정이 없음. ID 자체가 잘못된 경우와 구분되지 않으므로, 사용자에게
--all-dates또는 다른 ID 확인을 안내한다. - Interpark `data: []`: goods_code 가 지나갔거나 아직 오픈 전 / 비공개. 다른 ID 확인을 안내한다.
- HTTP 4xx/5xx: 차단/일시 장애. 우회 시도하지 않고
http error출력 후 종료. - JSON 스키마 변경: YES24 axPerfRemainSeat 는 HTML 응답을 정규식으로 파싱 — 사이트 갱신 시 영향 가능.
remain0 으로 잘못 보고될 수 있어 사용자에게 "조회 시각 기준" 이라고 표기. - 공연 매진: API 는
remain: 0반환. 매진 표시.
Response style
- 잔여석은 "조회 시각 기준" 으로 표현한다 (실시간 변동).
- "매크로", "선점", "오픈런", "자동 예매" 표현 금지 — 이 스킬은 조회 전용.
- 잔여석 수치 + 등급명 만 인용. 좌석 번호 / 좌석 위치는 노출하지 않는다.
- "지금 사라" 같은 행위 유도 금지 — 사용자가 직접 페이지에서 결제.
Notes
- 본 스킬은 의도적으로 예매 / 결제 / 좌석선택 / 로그인 자동화 를 포함하지 않는다. 매크로를 통한 입장권 부정구매·판매는 공연법 §4조의2 (2023.9.22 시행) 에 의해 형사처벌 대상.
- 시크릿 / 키 / 로그인 세션 일체 사용하지 않는다.
- Rate limit:
seats명령은 회차별 순차 호출 — Interpark 0.3s, YES24 0.4s 간격. 100회차 짜리 공연이면 약 30s ~ 40s 소요. 짧은 모니터링 루프에 넣지 말 것.
Done when
- 공연 URL 또는
platform:id가 확인되었다. - 일정 또는 잔여석 결과 JSON 을 반환하거나, 빈 결과 사유를 설명했다.
- 예매 / 결제 / 좌석 선택 기능을 자동화하지 않았다.
- 조회 시각 기준임을 안내했다.
#!/usr/bin/env python3
"""ticket-availability — YES24 / 인터파크 공연 일정 + 잔여석 조회 CLI.
조회 전용. 예매·결제·로그인 자동화 없음.
공연법 §4조의2 (매크로 입장권 부정구매·판매 금지) 비적용.
Usage:
ticket-availability schedule <url>
ticket-availability seats <url> [--all-dates]
ticket-availability health
Supported URLs:
YES24: https://ticket.yes24.com/Perf/<perf_id>
https://ticket.yes24.com/New/Perf/Detail/View/<perf_id>
yes24:<perf_id>
인터파크: https://tickets.interpark.com/goods/<goods_code>
interpark:<goods_code>
"""
from __future__ import annotations
import argparse
import json
import re
import sys
import time
from datetime import datetime
from typing import Any
try:
import httpx
except ModuleNotFoundError: # pragma: no cover - depends on user environment
httpx = None
class MissingHttpxError(RuntimeError):
"""Raised when the optional httpx runtime dependency is unavailable."""
def _require_httpx():
if httpx is None:
raise MissingHttpxError(
"Python package 'httpx' is required. Install it with: python3 -m pip install httpx"
)
return httpx
HTTPX_HTTP_ERROR = (
getattr(httpx, "HTTPError", MissingHttpxError) if httpx else MissingHttpxError
)
# ── URL Parsing ───────────────────────────────────────────────────────────────
def parse_url(url: str) -> tuple[str, str]:
"""Return (platform, id). Accepts full URL or `platform:id` shorthand."""
if url.startswith("yes24:"):
return "yes24", url[6:]
if url.startswith("interpark:"):
return "interpark", url[10:]
m = re.search(
r"yes24\.com/(?:[Nn]ew/)?[Pp]erf/(?:[Dd]etail/)?(?:[Vv]iew/)?(\d+)", url
)
if m:
return "yes24", m.group(1)
m = re.search(r"interpark\.com/goods/(\d+)", url, re.IGNORECASE)
if m:
return "interpark", m.group(1)
if re.fullmatch(r"\d+", url):
raise ValueError(
f"플랫폼을 명시하세요: yes24:{url} 또는 interpark:{url}"
)
raise ValueError(f"URL을 인식할 수 없습니다: {url}")
def _fmt_date(d: str) -> str:
if d and len(d) == 8 and d.isdigit():
return f"{d[:4]}-{d[4:6]}-{d[6:]}"
return d
def _fmt_time(t: str) -> str:
if t and len(t) == 4 and t.isdigit():
return f"{t[:2]}:{t[2:]}"
return t
# ── HTTP Setup ────────────────────────────────────────────────────────────────
UA = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"Chrome/124.0.0.0 Safari/537.36"
)
HEADERS_YES24 = {
"User-Agent": UA,
"Referer": "https://ticket.yes24.com/",
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "*/*",
"X-Requested-With": "XMLHttpRequest",
}
HEADERS_INTERPARK = {
"User-Agent": UA,
"Referer": "https://tickets.interpark.com/",
"Accept": "application/json",
}
YES24_BASE = "https://ticket.yes24.com"
INTERPARK_BASE = "https://api-ticketfront.interpark.com"
# ── YES24 Client ──────────────────────────────────────────────────────────────
class Yes24Client:
def __init__(self) -> None:
http = _require_httpx()
self.http = http.Client(
headers=HEADERS_YES24, timeout=20, follow_redirects=True
)
def _dates(self, perf_id: str, month_count: int) -> list[str]:
now = datetime.now()
months: list[str] = []
for delta in range(month_count):
month = now.month + delta
year = now.year + (month - 1) // 12
month = ((month - 1) % 12) + 1
months.append(f"{year:04d}-{month:02d}")
dates: list[str] = []
cutoff = now.strftime("%Y%m%d")
for month_str in months:
r = self.http.post(
f"{YES24_BASE}/New/Perf/Sale/Ajax/axPerfDay.aspx",
data={
"pGetMode": "days",
"pIdPerf": perf_id,
"pPerfMonth": month_str,
"pIdCode": "",
"pIsMania": "0",
},
)
r.raise_for_status()
text = r.text.strip().strip(",")
if not text:
continue
for raw in text.split(","):
d = raw.strip()
if not d:
continue
normalized = d.replace("-", "")
if normalized >= cutoff:
dates.append(normalized)
return sorted(set(dates))
def get_dates(self, perf_id: str) -> list[str]:
"""Available dates within ~3 weeks (fast)."""
return self._dates(perf_id, month_count=3)
def get_all_dates(self, perf_id: str) -> list[str]:
"""Available dates across 6 months (full schedule)."""
return self._dates(perf_id, month_count=6)
def get_slots(self, perf_id: str, perf_day: str) -> list[dict]:
r = self.http.post(
f"{YES24_BASE}/NEw/Perf/Detail/Ajax/axPerfPlayTime.aspx",
data={"IdPerf": perf_id, "PerfDay": perf_day},
)
r.raise_for_status()
html = r.text
slots: list[dict] = []
seen: set[str] = set()
for m in re.finditer(r"idTime='(\d+)'", html):
id_time = m.group(1)
if id_time in seen:
continue
seen.add(id_time)
ctx_start = max(0, m.start() - 200)
ctx = html[ctx_start : m.end() + 200]
time_m = re.search(r"(\d{1,2}:\d{2}|\d[회]|[12]\d{3}회)", ctx)
label = time_m.group(0) if time_m else id_time
slots.append({"idTime": id_time, "label": label})
return slots
def get_seats(self, id_time: str) -> list[dict]:
r = self.http.post(
f"{YES24_BASE}/New/Perf/Detail/Ajax/axPerfRemainSeat.aspx",
data={"Type": "calendar", "IdTime": id_time, "IdLock": "0"},
)
r.raise_for_status()
html = r.text
seats: list[dict] = []
for m in re.finditer(
r"<dt>([^<]+)</dt>\s*<dd>([^<]*)<span[^>]*>\(잔여:(\d+)석\)</span>",
html,
):
seats.append(
{
"grade": m.group(1).strip(),
"price": m.group(2).strip().rstrip(",").strip(),
"remain": int(m.group(3)),
}
)
if not seats:
for i, m in enumerate(re.finditer(r"\(잔여:(\d+)석\)", html)):
seats.append({"grade": f"좌석{i+1}", "price": "", "remain": int(m.group(1))})
return seats
def schedule(self, perf_id: str, all_dates: bool) -> list[dict]:
"""Schedule = dates × slots flattened. No seat lookup."""
dates = self.get_all_dates(perf_id) if all_dates else self.get_dates(perf_id)
out: list[dict] = []
for d in dates:
for slot in self.get_slots(perf_id, d):
out.append(
{
"date": _fmt_date(d),
"time_label": slot["label"],
"id_time": slot["idTime"],
}
)
return out
def all_seats(self, perf_id: str, all_dates: bool) -> dict:
result: dict = {}
dates = self.get_all_dates(perf_id) if all_dates else self.get_dates(perf_id)
for d in dates:
for slot in self.get_slots(perf_id, d):
seats = self.get_seats(slot["idTime"])
key = f"{_fmt_date(d)}|{slot['label']}"
result[key] = {
"date": _fmt_date(d),
"time_label": slot["label"],
"id_time": slot["idTime"],
"seats": seats,
}
time.sleep(0.4)
return result
# ── Interpark Client ──────────────────────────────────────────────────────────
class InterparkClient:
def __init__(self) -> None:
http = _require_httpx()
self.http = http.Client(
headers=HEADERS_INTERPARK, timeout=20, follow_redirects=True
)
def get_schedule(self, goods_code: str) -> list[dict]:
now = datetime.now()
r = self.http.get(
f"{INTERPARK_BASE}/v1/goods/{goods_code}/playSeq",
params={
"goodsCode": goods_code,
"isBookableDate": "true",
"page": "1",
"pageSize": "200",
"startDate": now.strftime("%Y%m%d"),
"endDate": f"{now.year + 1}{now.month:02d}{now.day:02d}",
},
)
r.raise_for_status()
data = r.json()
if isinstance(data, list):
return data
return data.get("response", {}).get("data") or data.get("data") or []
def get_seats(self, goods_code: str, play_seq: str) -> list[dict]:
r = self.http.get(
f"{INTERPARK_BASE}/v1/goods/{goods_code}/playSeq/PlaySeq/{play_seq}/REMAINSEAT"
)
r.raise_for_status()
data = r.json()
if isinstance(data, dict):
return (
data.get("remainSeat")
or (data.get("data") or {}).get("remainSeat")
or data.get("response", {}).get("remainSeat")
or []
)
return []
def schedule(self, goods_code: str) -> list[dict]:
out: list[dict] = []
for item in self.get_schedule(goods_code):
out.append(
{
"date": _fmt_date(item.get("playDate", "")),
"time": _fmt_time(item.get("playTime", "")),
"play_seq": item.get("playSeq", ""),
}
)
return out
def all_seats(self, goods_code: str) -> dict:
result: dict = {}
for item in self.get_schedule(goods_code):
seq = item.get("playSeq", "")
if not seq:
continue
seats_raw = self.get_seats(goods_code, seq)
normalized = [
{
"grade": s.get("seatGradeName", s.get("seatGrade", "")),
"remain": int(s.get("remainCnt", 0)),
}
for s in seats_raw
]
key = f"{_fmt_date(item.get('playDate', ''))}|{_fmt_time(item.get('playTime', ''))}|{seq}"
result[key] = {
"date": _fmt_date(item.get("playDate", "")),
"time": _fmt_time(item.get("playTime", "")),
"play_seq": seq,
"seats": normalized,
}
time.sleep(0.3)
return result
# ── CLI ───────────────────────────────────────────────────────────────────────
def _dump(obj: Any, compact: bool) -> str:
if compact:
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
return json.dumps(obj, ensure_ascii=False, indent=2)
def cmd_schedule(args: argparse.Namespace) -> int:
platform, pid = parse_url(args.url)
if platform == "yes24":
out = Yes24Client().schedule(pid, all_dates=args.all_dates)
else:
out = InterparkClient().schedule(pid)
print(_dump({"platform": platform, "id": pid, "schedule": out}, args.compact))
return 0
def cmd_seats(args: argparse.Namespace) -> int:
platform, pid = parse_url(args.url)
if platform == "yes24":
out = Yes24Client().all_seats(pid, all_dates=args.all_dates)
else:
out = InterparkClient().all_seats(pid)
print(_dump({"platform": platform, "id": pid, "seats": out}, args.compact))
return 0
def cmd_health(args: argparse.Namespace) -> int:
http = _require_httpx()
results: dict = {}
for name, url in [
("yes24",
f"{YES24_BASE}/New/Perf/Sale/Ajax/axPerfDay.aspx"),
("interpark",
f"{INTERPARK_BASE}/v1/goods/00000000/playSeq"),
]:
try:
if name == "yes24":
r = http.post(url, headers=HEADERS_YES24,
data={"pGetMode": "days", "pIdPerf": "0",
"pPerfMonth": "2000-01", "pIdCode": "",
"pIsMania": "0"}, timeout=10)
else:
r = http.get(url, headers=HEADERS_INTERPARK,
params={"goodsCode": "00000000",
"isBookableDate": "true",
"page": "1", "pageSize": "1",
"startDate": "20000101",
"endDate": "20000102"},
timeout=10)
results[name] = {"status": r.status_code, "ok": r.status_code < 500}
except Exception as e:
results[name] = {"status": 0, "ok": False, "error": str(e)}
print(_dump(results, args.compact))
return 0 if all(v.get("ok") for v in results.values()) else 1
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(
prog="ticket-availability",
description="YES24 / 인터파크 공연 일정 + 잔여석 조회 (조회 전용)",
)
sub = parser.add_subparsers(dest="cmd", required=True)
def _common(p: argparse.ArgumentParser) -> None:
p.add_argument("--compact", action="store_true",
help="One-line JSON (기본: 들여쓰기 출력)")
p_sch = sub.add_parser("schedule", help="공연 일정 조회")
p_sch.add_argument("url", help="공연 URL 또는 platform:id")
p_sch.add_argument("--all-dates", action="store_true",
help="YES24 — 6개월 전체 (기본: 3주)")
_common(p_sch)
p_sch.set_defaults(func=cmd_schedule)
p_st = sub.add_parser("seats", help="등급별 잔여석 조회 (전 일정)")
p_st.add_argument("url", help="공연 URL 또는 platform:id")
p_st.add_argument("--all-dates", action="store_true",
help="YES24 — 6개월 전체 (기본: 3주)")
_common(p_st)
p_st.set_defaults(func=cmd_seats)
p_h = sub.add_parser("health", help="API endpoint reachability check")
_common(p_h)
p_h.set_defaults(func=cmd_health)
args = parser.parse_args(argv)
try:
return args.func(args)
except ValueError as e:
print(f"error: {e}", file=sys.stderr)
return 2
except MissingHttpxError as e:
print(f"dependency error: {e}", file=sys.stderr)
return 4
except HTTPX_HTTP_ERROR as e:
print(f"http error: {e}", file=sys.stderr)
return 3
if __name__ == "__main__":
sys.exit(main())
Related skills
How it compares
Use ticket-availability for quick read-only YES24 and Interpark seat lookups when you do not want to maintain custom scraping scripts.
FAQ
What does ticket-availability do?
YES24 / 인터파크 공연의 공개 일정 + 등급별 잔여석을 단일 HTTP 호출로 조회 (조회 전용, 예매·결제 없음).
When should I use ticket-availability?
User asks about ticket availability or related SKILL.md workflows.
Is ticket-availability safe to install?
Review the Security Audits panel on this page before installing in production.