Channels and latest uploads
Resolve a public channel once, then use its stable ID to check the newest uploads. Both endpoints require your Bearer API key and cost 0 credits, with X-Credits-Charged: 0. The keyed MCP tools are resolve_channel(input) and get_latest_videos(channel | playlist); the keyless server accepts the same latest-upload inputs. Pass exactly one nonblank channel or playlist string, up to 300 characters, with no additional fields.
Resolution allows 100 calls per free key per UTC day, or 1,000 per paid key. Latest uploads has a separate allowance of 500 free or 5,000 paid calls/day. Valid admitted calls count even on cached results. Each allowance is checked before upstream work; exhaustion returns 429 QuotaExceeded with Retry-After until 00:00 UTC. The ordinary key request rate also applies. Keyless MCP latest has its own 30 calls per IP address/day, including feed-cache hits, without consuming cold transcript allowance. See credits and limits.
Resolve a channel
GET /v1/channels/resolve?input= accepts a handle such as @TED, a public YouTube channel URL, or a UC channel ID. /channel/, /user/, and /c/ URL forms are supported; legacy /c/ names are resolved as handles. Successful resolution is cached. If YouTube confirms that a valid channel input has no matching public channel, the API returns 404 InvalidInput and remembers that result for 24 hours.
That missing-channel result is shared across keys for the same normalized input. Repeating it within 24 hours uses no additional YouTube Data API units, but each admitted valid call still counts toward the key's daily resolution allowance. Transient upstream errors, configuration errors, and quota failures are not cached as missing channels.
{
"ok": true,
"channel_id": "UCAuUUnT6oDeKwE6v1NGQxug",
"handle": "@TED",
"title": "TED",
"resolved_from": "@TED",
"uploads_playlist_id": "UUAuUUnT6oDeKwE6v1NGQxug"
}
Keep channel_id for stable references and uploads_playlist_id when you need the channel's complete upload list. handle may be null when YouTube does not report one.
Read the latest uploads
GET /v1/channels/latest accepts exactly one of channel or playlist. Use the UC channel ID from resolution, or a public playlist ID or URL. The response contains at most the newest 15 entries available in YouTube's public RSS feed, ordered newest first and cached for ten minutes. An empty feed returns an empty videos array.
A channel URL containing a UC ID also works directly. A handle or legacy channel alias works after resolution has cached it. An unknown alias returns 400 with channel_resolution_required and resolve_url; call the free resolve endpoint first and pass its channel_id. Latest uploads reads RSS only and never spends Data API or proxy capacity.
{
"ok": true,
"videos": [
{
"video_id": "jNQXAC9IVRw",
"title": "Me at the zoo",
"published_at": "2005-04-23T00:00:00Z",
"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw",
"cached": true
}
]
}
The example illustrates the feed response shape. cached is a recorded hint that a transcript is available in the shared cache, not a check of the stored object during this request. A missing object can briefly leave a stale hint until background indexing or backfill corrects it. Reading the feed does not fetch transcripts or check storage separately for each video; the transcript response reports the actual cache result and charge. Fetch selected videos with transcripts, or enumerate the full channel or playlist through listing. The RSS window is limited to 15 entries and is not a complete history or a guarantee that every upload from the past week is included.
If YouTube returns 404 for a well-formed channel or playlist feed ID, latest uploads returns 404 NotFound with retryable: false, at zero credits. Check the ID before retrying. This differs from channel resolution's cached 404 InvalidInput: resolution checks channel identity, while latest uploads reads the public RSS feed. Other upstream feed failures remain retryable UpstreamError responses.
Latest uploads in an assistant
Both MCP servers accept a public playlist ID or URL, as well as a channel. For example, call get_latest_videos with {"playlist":"UUAuUUnT6oDeKwE6v1NGQxug"} to read TED’s uploads playlist. An equivalent playlist URL shares the same ten-minute feed cache. Channel and playlist calls use the same latest-upload allowance; switching source type does not create another allowance.
Resolve and check uploads in four languages
These examples resolve TED and pass its stable channel ID to the latest-uploads endpoint. Maintained runnable variants, including playlist latest uploads, are in the examples directory.
curl --get https://api.scripthaul.com/v1/channels/resolve \
-H "Authorization: Bearer $SCRIPTHAUL_API_KEY" --data-urlencode "input=@TED"
curl --get https://api.scripthaul.com/v1/channels/latest \
-H "Authorization: Bearer $SCRIPTHAUL_API_KEY" \
--data-urlencode "channel=UCAuUUnT6oDeKwE6v1NGQxug"
import json, os, urllib.parse, urllib.request
headers = {"Authorization": f"Bearer {os.environ['SCRIPTHAUL_API_KEY']}"}
def read(path, params):
request = urllib.request.Request("https://api.scripthaul.com/v1/" + path + "?" +
urllib.parse.urlencode(params), headers=headers)
with urllib.request.urlopen(request) as response:
return json.load(response)
channel = read("channels/resolve", {"input": "@TED"})
print(read("channels/latest", {"channel": channel["channel_id"]}))
const headers = { Authorization: `Bearer ${process.env.SCRIPTHAUL_API_KEY}` };
const resolved = await fetch("https://api.scripthaul.com/v1/channels/resolve?input=%40TED", { headers });
const channel = await resolved.json();
const url = new URL("https://api.scripthaul.com/v1/channels/latest");
url.searchParams.set("channel", channel.channel_id);
const latest = await fetch(url, { headers });
console.log(await latest.json());
read := func(endpoint string) *http.Response {
req, _ := http.NewRequest("GET", endpoint, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("SCRIPTHAUL_API_KEY"))
response, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
return response
}
resolved := read("https://api.scripthaul.com/v1/channels/resolve?input=%40TED")
var channel struct { ID string `json:"channel_id"` }
if err := json.NewDecoder(resolved.Body).Decode(&channel); err != nil { log.Fatal(err) }
resolved.Body.Close()
latest := read("https://api.scripthaul.com/v1/channels/latest?channel="+url.QueryEscape(channel.ID))
defer latest.Body.Close()
io.Copy(os.Stdout, latest.Body)
Errors
Send one source per latest request: missing sources, both sources together, and malformed input return 400. Missing or invalid keys return 401, frozen accounts return 403, and the shared per-key rate limit returns 429 with Retry-After. An unavailable feed or upstream failure returns a typed error; it does not consume credits. See the error reference.