@acedatacloud/skills 2026.606.1 → 2026.613.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/blogger/SKILL.md +68 -0
- package/skills/devto/SKILL.md +78 -0
- package/skills/linkedin/SKILL.md +73 -0
- package/skills/reddit/SKILL.md +79 -0
- package/skills/youtube/SKILL.md +152 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.613.1",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: blogger
|
|
3
|
+
description: Publish posts to your Blogger blog and read your blogs / posts via the Blogger API v3. Use when the user mentions Blogger, blogspot, publishing a post to their blog, listing their blogs, or updating an existing Blogger post.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to publish a post to their Blogger blog,
|
|
6
|
+
list their blogs, list / read posts on a blog, or update an existing
|
|
7
|
+
post. The connector grants the Blogger scope (read + write); confirm
|
|
8
|
+
before publishing publicly (you can insert as a draft first).
|
|
9
|
+
connections: [google/blogger]
|
|
10
|
+
allowed_tools: [Bash]
|
|
11
|
+
license: Apache-2.0
|
|
12
|
+
metadata:
|
|
13
|
+
author: acedatacloud
|
|
14
|
+
version: "1.0"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Call the **Blogger API v3** with `curl + jq`. The user's OAuth bearer token is
|
|
18
|
+
in `$GOOGLE_BLOGGER_TOKEN`; every call needs
|
|
19
|
+
`Authorization: Bearer $GOOGLE_BLOGGER_TOKEN`. Base URL:
|
|
20
|
+
`https://www.googleapis.com/blogger/v3`.
|
|
21
|
+
|
|
22
|
+
Errors are `{"error": {"code": ..., "message": ...}}` — show them verbatim.
|
|
23
|
+
`401` → token expired, re-connect the Blogger connector.
|
|
24
|
+
|
|
25
|
+
**Always start by listing the user's blogs** to get a `blogId`:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_BLOGGER_TOKEN" \
|
|
29
|
+
"https://www.googleapis.com/blogger/v3/users/self/blogs" \
|
|
30
|
+
| jq '.items[] | {id, name, url}'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Publish a post
|
|
34
|
+
|
|
35
|
+
**Confirm before publishing publicly.** Use `?isDraft=true` to stage a draft.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
BLOG_ID="1234567890"
|
|
39
|
+
jq -n --arg t "My title" --arg c "<p>HTML content of the post…</p>" \
|
|
40
|
+
'{kind:"blogger#post", title:$t, content:$c, labels:["ai","video"]}' \
|
|
41
|
+
| curl -sS -X POST \
|
|
42
|
+
"https://www.googleapis.com/blogger/v3/blogs/$BLOG_ID/posts/?isDraft=false" \
|
|
43
|
+
-H "Authorization: Bearer $GOOGLE_BLOGGER_TOKEN" \
|
|
44
|
+
-H "Content-Type: application/json" \
|
|
45
|
+
-d @- \
|
|
46
|
+
| jq '{id, url, status}'
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`content` is **HTML** (not Markdown) — convert Markdown to HTML first
|
|
50
|
+
(e.g. with `pandoc -f markdown -t html` or a simple converter).
|
|
51
|
+
|
|
52
|
+
- Publish a staged draft: `POST /blogs/{blogId}/posts/{postId}/publish`.
|
|
53
|
+
- Update a post: `PUT /blogs/{blogId}/posts/{postId}` with the same shape.
|
|
54
|
+
|
|
55
|
+
## List / read posts
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_BLOGGER_TOKEN" \
|
|
59
|
+
"https://www.googleapis.com/blogger/v3/blogs/$BLOG_ID/posts?maxResults=20&status=live" \
|
|
60
|
+
| jq '.items[] | {id, title, url, published}'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Gotchas
|
|
64
|
+
|
|
65
|
+
- **Enable the Blogger API** on the Google Cloud project backing the OAuth
|
|
66
|
+
client, or calls 403 with `accessNotConfigured`.
|
|
67
|
+
- `content` must be HTML; passing raw Markdown will render literally.
|
|
68
|
+
- Paginate with `&pageToken=$PAGE_TOKEN` from the previous `.nextPageToken`.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devto
|
|
3
|
+
description: Publish, update and read articles on DEV (dev.to) via the Forem API v1. Use when the user mentions dev.to / DEV Community, publishing a blog post to dev.to, cross-posting an article, updating a published post, or listing their dev.to articles and stats.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to publish a Markdown article to their
|
|
6
|
+
dev.to account, update a previously published article, or list /
|
|
7
|
+
inspect their own dev.to articles (views, reactions, comments). The
|
|
8
|
+
connector stores a DEV API Key with full account access — confirm
|
|
9
|
+
before publishing publicly (you can publish as a draft with
|
|
10
|
+
published=false first).
|
|
11
|
+
connections: [devto]
|
|
12
|
+
allowed_tools: [Bash]
|
|
13
|
+
license: Apache-2.0
|
|
14
|
+
metadata:
|
|
15
|
+
author: acedatacloud
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Call the **Forem API v1** (dev.to) with `curl + jq`. The user's API key is in
|
|
20
|
+
`$DEVTO_API_KEY`; every call needs the headers `api-key: $DEVTO_API_KEY` and
|
|
21
|
+
`Accept: application/vnd.forem.api-v1+json`. Base URL: `https://dev.to/api`.
|
|
22
|
+
|
|
23
|
+
Errors come back as JSON with an `error` / `status` field — show them verbatim.
|
|
24
|
+
`401` means the API key is invalid → the user must re-connect the DEV connector.
|
|
25
|
+
|
|
26
|
+
**Always start by confirming the key** and learning the account:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl -sS -H "api-key: $DEVTO_API_KEY" -H "Accept: application/vnd.forem.api-v1+json" \
|
|
30
|
+
"https://dev.to/api/users/me" | jq '{username, name}'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Publish an article
|
|
34
|
+
|
|
35
|
+
**Confirm with the user before publishing publicly.** Default to a draft
|
|
36
|
+
(`published:false`) unless they explicitly say publish/now.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
TITLE="My title"
|
|
40
|
+
BODY_MD="$(cat article.md)" # full Markdown body
|
|
41
|
+
jq -n --arg t "$TITLE" --arg b "$BODY_MD" \
|
|
42
|
+
'{article:{title:$t, body_markdown:$b, published:false, tags:["ai","webdev"]}}' \
|
|
43
|
+
| curl -sS -X POST "https://dev.to/api/articles" \
|
|
44
|
+
-H "api-key: $DEVTO_API_KEY" \
|
|
45
|
+
-H "Accept: application/vnd.forem.api-v1+json" \
|
|
46
|
+
-H "Content-Type: application/json" \
|
|
47
|
+
-d @- \
|
|
48
|
+
| jq '{id, url, published}'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To publish a draft later (or edit), `PUT /api/articles/{id}` with the same
|
|
52
|
+
shape (set `published:true`). Front-matter inside `body_markdown` (a `---`
|
|
53
|
+
block) can also carry `title`, `tags`, `series`, `canonical_url`, `cover_image`.
|
|
54
|
+
|
|
55
|
+
- **Canonical URL:** when cross-posting, set
|
|
56
|
+
`"canonical_url":"https://your-blog/original"` so DEV points SEO back to the
|
|
57
|
+
source — important for the article→video / cross-publishing flow.
|
|
58
|
+
|
|
59
|
+
## List / inspect my articles
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# My published + draft articles (paginated; per_page max 1000).
|
|
63
|
+
curl -sS -H "api-key: $DEVTO_API_KEY" -H "Accept: application/vnd.forem.api-v1+json" \
|
|
64
|
+
"https://dev.to/api/articles/me/all?per_page=30" \
|
|
65
|
+
| jq '.[] | {id, title, published, page_views_count, public_reactions_count, comments_count}'
|
|
66
|
+
|
|
67
|
+
# A single article's full content + stats.
|
|
68
|
+
curl -sS -H "api-key: $DEVTO_API_KEY" -H "Accept: application/vnd.forem.api-v1+json" \
|
|
69
|
+
"https://dev.to/api/articles/ARTICLE_ID" | jq '{title, url, reactions: .public_reactions_count, views: .page_views_count}'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Gotchas
|
|
73
|
+
|
|
74
|
+
- **Tags:** max 4, lowercase, no spaces (e.g. `webdev`, `machinelearning`).
|
|
75
|
+
- **Rate limit:** article create/update is throttled (a few per 30s); space out
|
|
76
|
+
bulk publishes or you'll get `429`.
|
|
77
|
+
- `body_markdown` is the source of truth — if you put a `---` front-matter block
|
|
78
|
+
at the top, its fields override the JSON `article` fields.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: linkedin
|
|
3
|
+
description: Publish text / link posts to your LinkedIn personal feed via the LinkedIn Posts API. Use when the user mentions LinkedIn, sharing or posting an update to LinkedIn, or cross-posting an article / link to their LinkedIn feed.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to publish a text or link share to their
|
|
6
|
+
own LinkedIn feed. Posting is public on their profile — confirm the
|
|
7
|
+
text (and link, if any) before publishing. Requires the
|
|
8
|
+
w_member_social scope (granted by the connector at install).
|
|
9
|
+
connections: [linkedin]
|
|
10
|
+
allowed_tools: [Bash]
|
|
11
|
+
license: Apache-2.0
|
|
12
|
+
metadata:
|
|
13
|
+
author: acedatacloud
|
|
14
|
+
version: "1.0"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Call the **LinkedIn API** with `curl + jq`. The user's bearer token is in
|
|
18
|
+
`$LINKEDIN_TOKEN`; every call needs `Authorization: Bearer $LINKEDIN_TOKEN`.
|
|
19
|
+
|
|
20
|
+
LinkedIn posts must be authored by the member's URN. Get the member id from the
|
|
21
|
+
OpenID userinfo endpoint, then build `urn:li:person:{sub}`.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
SUB=$(curl -sS -H "Authorization: Bearer $LINKEDIN_TOKEN" \
|
|
25
|
+
"https://api.linkedin.com/v2/userinfo" | jq -r .sub)
|
|
26
|
+
AUTHOR="urn:li:person:$SUB"
|
|
27
|
+
echo "$AUTHOR"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Errors are JSON with `message` / `serviceErrorCode` — show them verbatim.
|
|
31
|
+
`401` → token expired (tokens last ~60 days), re-connect the LinkedIn connector.
|
|
32
|
+
|
|
33
|
+
## Publish a post (Posts API)
|
|
34
|
+
|
|
35
|
+
**Confirm the text with the user first.** Use the versioned Posts API; set the
|
|
36
|
+
`LinkedIn-Version` header to a recent `YYYYMM` (LinkedIn requires a valid recent
|
|
37
|
+
version — if you get `426`/version errors, bump to the current month).
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
jq -n --arg a "$AUTHOR" --arg t "My update text. Check out https://studio.acedata.cloud" \
|
|
41
|
+
'{author:$a, commentary:$t, visibility:"PUBLIC",
|
|
42
|
+
distribution:{feedDistribution:"MAIN_FEED", targetEntities:[], thirdPartyDistributionChannels:[]},
|
|
43
|
+
lifecycleState:"PUBLISHED", isReshareDisabledByAuthor:false}' \
|
|
44
|
+
| curl -sS -X POST "https://api.linkedin.com/rest/posts" \
|
|
45
|
+
-H "Authorization: Bearer $LINKEDIN_TOKEN" \
|
|
46
|
+
-H "Content-Type: application/json" \
|
|
47
|
+
-H "LinkedIn-Version: 202401" \
|
|
48
|
+
-H "X-Restli-Protocol-Version: 2.0.0" \
|
|
49
|
+
-d @- -D - -o /dev/null | tr -d '\r' | awk '/^[Xx]-[Rr]estli-[Ii]d:|^[Ll]ocation:/{print}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
A successful create returns `201` with the post URN in the `x-restli-id`
|
|
53
|
+
(or `Location`) response header — report that URN / the resulting post URL.
|
|
54
|
+
|
|
55
|
+
> Link posts: put the URL inline in `commentary` (LinkedIn auto-unfurls it).
|
|
56
|
+
> Rich article attachments need the assets/images API — out of scope for a
|
|
57
|
+
> simple text/link share; keep links inline.
|
|
58
|
+
|
|
59
|
+
## Fallback: legacy ugcPosts
|
|
60
|
+
|
|
61
|
+
If the versioned endpoint is unavailable for the app, the older
|
|
62
|
+
`POST https://api.linkedin.com/v2/ugcPosts` with a
|
|
63
|
+
`specificContent."com.linkedin.ugc.ShareContent"` body also works with
|
|
64
|
+
`w_member_social`. Prefer `/rest/posts` above.
|
|
65
|
+
|
|
66
|
+
## Gotchas
|
|
67
|
+
|
|
68
|
+
- **Author URN** must match the token's member (`urn:li:person:{sub}`) — you
|
|
69
|
+
can't post as someone else.
|
|
70
|
+
- `w_member_social` only allows posting to the **member's own feed**; company
|
|
71
|
+
Page posts need `w_organization_social` + an admin role (not in this connector).
|
|
72
|
+
- The `LinkedIn-Version` header is mandatory for `/rest/*`; a stale value
|
|
73
|
+
returns a version error — bump to the current `YYYYMM`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reddit
|
|
3
|
+
description: Submit posts (link or text) to subreddits and read your Reddit identity / content via the Reddit API. Use when the user mentions Reddit, posting to a subreddit, submitting a link or self-post, or checking their Reddit profile / submissions.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to submit a post to a subreddit (link or
|
|
6
|
+
self/text post), or read their own Reddit identity and submissions.
|
|
7
|
+
Posting to a subreddit is public and subject to that subreddit's rules
|
|
8
|
+
/ karma requirements — confirm the target subreddit, title and body
|
|
9
|
+
before submitting.
|
|
10
|
+
connections: [reddit]
|
|
11
|
+
allowed_tools: [Bash]
|
|
12
|
+
license: Apache-2.0
|
|
13
|
+
metadata:
|
|
14
|
+
author: acedatacloud
|
|
15
|
+
version: "1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Call the **Reddit API** (OAuth endpoints) with `curl + jq`. The user's bearer
|
|
19
|
+
token is in `$REDDIT_TOKEN`. **Every call MUST send a `User-Agent` header** or
|
|
20
|
+
Reddit returns `429`. Use the OAuth host `https://oauth.reddit.com`.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
UA="web:cloud.acedata.connectors:v1.0 (by /u/acedatacloud)"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Errors are JSON; a submit returns `{"json":{"errors":[...], "data":{...}}}` —
|
|
27
|
+
if `errors` is non-empty, show them verbatim. `401` → token expired, re-connect.
|
|
28
|
+
|
|
29
|
+
**Always start by confirming identity:**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -sS -H "Authorization: Bearer $REDDIT_TOKEN" -H "User-Agent: $UA" \
|
|
33
|
+
"https://oauth.reddit.com/api/v1/me" | jq '{name, total_karma, link_karma}'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Submit a post
|
|
37
|
+
|
|
38
|
+
**Confirm the subreddit + title + body with the user first.** `sr` is the
|
|
39
|
+
subreddit name WITHOUT the `r/` prefix.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Self (text) post: kind=self + text. Link post: kind=link + url.
|
|
43
|
+
curl -sS -X POST "https://oauth.reddit.com/api/submit" \
|
|
44
|
+
-H "Authorization: Bearer $REDDIT_TOKEN" -H "User-Agent: $UA" \
|
|
45
|
+
--data-urlencode "sr=test" \
|
|
46
|
+
--data-urlencode "kind=self" \
|
|
47
|
+
--data-urlencode "title=My title" \
|
|
48
|
+
--data-urlencode "text=My self-post body in markdown" \
|
|
49
|
+
--data-urlencode "api_type=json" \
|
|
50
|
+
| jq '.json | {errors, url: .data.url, id: .data.id}'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For a link post:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
curl -sS -X POST "https://oauth.reddit.com/api/submit" \
|
|
57
|
+
-H "Authorization: Bearer $REDDIT_TOKEN" -H "User-Agent: $UA" \
|
|
58
|
+
--data-urlencode "sr=test" --data-urlencode "kind=link" \
|
|
59
|
+
--data-urlencode "title=My title" --data-urlencode "url=https://example.com" \
|
|
60
|
+
--data-urlencode "api_type=json" | jq '.json'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Read my submissions
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
curl -sS -H "Authorization: Bearer $REDDIT_TOKEN" -H "User-Agent: $UA" \
|
|
67
|
+
"https://oauth.reddit.com/user/USERNAME/submitted?limit=10" \
|
|
68
|
+
| jq '.data.children[] | .data | {title, subreddit, ups, num_comments, permalink}'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Gotchas
|
|
72
|
+
|
|
73
|
+
- **User-Agent is mandatory** on every request — omitting it → `429`.
|
|
74
|
+
- Many subreddits gate posting on **account age / karma / flair**; a submit can
|
|
75
|
+
return `errors` like `[["SUBREDDIT_NOTALLOWED", ...]]` — surface it and try
|
|
76
|
+
`r/test` to validate the flow.
|
|
77
|
+
- Respect rate limits: read the `X-Ratelimit-Remaining` response header; space
|
|
78
|
+
out bulk submits.
|
|
79
|
+
- Use `r/test` as a safe target when validating that the connection works.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: youtube
|
|
3
|
+
description: Search YouTube, read your own channel and uploaded videos (stats, comments), and upload new videos via the YouTube Data API v3. Use when the user mentions YouTube, "my channel", "my videos", searching YouTube, video views / likes / stats, uploading a video to YouTube, or checking how a published video is doing.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to search YouTube, inspect their own
|
|
6
|
+
channel / uploaded videos (views, likes, comments), look up a
|
|
7
|
+
video's stats, or upload a new video to their channel. The
|
|
8
|
+
installed connector always grants `youtube.readonly` (search + read
|
|
9
|
+
your channel and videos); the user opts in to `youtube.upload` at
|
|
10
|
+
install time to publish videos — confirm before uploading and
|
|
11
|
+
re-prompt to re-install if the upload scope is missing.
|
|
12
|
+
connections: [google/youtube]
|
|
13
|
+
allowed_tools: [Bash]
|
|
14
|
+
license: Apache-2.0
|
|
15
|
+
metadata:
|
|
16
|
+
author: acedatacloud
|
|
17
|
+
version: "1.0"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Call the **YouTube Data API v3** with `curl + jq`. The user's OAuth bearer
|
|
21
|
+
token is in `$GOOGLE_YOUTUBE_TOKEN`; every call needs it as
|
|
22
|
+
`Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN`. Base URL:
|
|
23
|
+
`https://www.googleapis.com/youtube/v3`.
|
|
24
|
+
|
|
25
|
+
The token always carries `youtube.readonly` plus identity scopes
|
|
26
|
+
(`openid email profile`); if the user opted in at install it also
|
|
27
|
+
carries `youtube.upload` (publish videos).
|
|
28
|
+
|
|
29
|
+
Responses are standard JSON; failures surface as
|
|
30
|
+
`{"error": {"code": 401|403|..., "message": "..."}}` — show that error
|
|
31
|
+
verbatim. `401` → token expired, the user must re-connect the YouTube
|
|
32
|
+
connector. `403 insufficientPermissions` on an upload → the user did
|
|
33
|
+
not grant `youtube.upload`; ask them to re-connect with the upload box
|
|
34
|
+
checked.
|
|
35
|
+
|
|
36
|
+
**Always start with the channel check** to confirm the connection works
|
|
37
|
+
and learn which channel you're operating against.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
41
|
+
"https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,contentDetails&mine=true" \
|
|
42
|
+
| jq '.items[0] | {title: .snippet.title, subs: .statistics.subscriberCount, views: .statistics.viewCount, uploads: .contentDetails.relatedPlaylists.uploads}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Search YouTube
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Public search (any video). type can be video|channel|playlist.
|
|
49
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
50
|
+
--data-urlencode "q=ai video automation" \
|
|
51
|
+
--data-urlencode "part=snippet" \
|
|
52
|
+
--data-urlencode "type=video" \
|
|
53
|
+
--data-urlencode "maxResults=10" \
|
|
54
|
+
-G "https://www.googleapis.com/youtube/v3/search" \
|
|
55
|
+
| jq '.items[] | {videoId: .id.videoId, title: .snippet.title, channel: .snippet.channelTitle, published: .snippet.publishedAt}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Add `--data-urlencode "order=date|viewCount|rating|relevance"` to sort,
|
|
59
|
+
or `--data-urlencode "publishedAfter=2026-01-01T00:00:00Z"` to window.
|
|
60
|
+
|
|
61
|
+
## See my uploaded videos
|
|
62
|
+
|
|
63
|
+
YouTube has no "list my videos" call directly — read the channel's
|
|
64
|
+
**uploads playlist**, then page its items.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# 1. Get the uploads playlist id (UU... ) — same as channels call above.
|
|
68
|
+
UPLOADS=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
69
|
+
"https://www.googleapis.com/youtube/v3/channels?part=contentDetails&mine=true" \
|
|
70
|
+
| jq -r '.items[0].contentDetails.relatedPlaylists.uploads')
|
|
71
|
+
|
|
72
|
+
# 2. List recent uploads (50/page; follow .nextPageToken for more).
|
|
73
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
74
|
+
-G "https://www.googleapis.com/youtube/v3/playlistItems" \
|
|
75
|
+
--data-urlencode "part=snippet,contentDetails" \
|
|
76
|
+
--data-urlencode "playlistId=$UPLOADS" \
|
|
77
|
+
--data-urlencode "maxResults=50" \
|
|
78
|
+
| jq '.items[] | {videoId: .contentDetails.videoId, title: .snippet.title, published: .snippet.publishedAt}'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Paginate by passing `--data-urlencode "pageToken=$PAGE_TOKEN"` with the
|
|
82
|
+
`.nextPageToken` from the previous response.
|
|
83
|
+
|
|
84
|
+
## Video stats (views / likes / comments)
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Accepts a comma-separated id list.
|
|
88
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
89
|
+
-G "https://www.googleapis.com/youtube/v3/videos" \
|
|
90
|
+
--data-urlencode "part=snippet,statistics,status" \
|
|
91
|
+
--data-urlencode "id=VIDEO_ID_1,VIDEO_ID_2" \
|
|
92
|
+
| jq '.items[] | {title: .snippet.title, views: .statistics.viewCount, likes: .statistics.likeCount, comments: .statistics.commentCount, privacy: .status.privacyStatus}'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Read comments on a video
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
99
|
+
-G "https://www.googleapis.com/youtube/v3/commentThreads" \
|
|
100
|
+
--data-urlencode "part=snippet" \
|
|
101
|
+
--data-urlencode "videoId=VIDEO_ID" \
|
|
102
|
+
--data-urlencode "maxResults=20" \
|
|
103
|
+
--data-urlencode "order=relevance" \
|
|
104
|
+
| jq '.items[] | .snippet.topLevelComment.snippet | {author: .authorDisplayName, text: .textDisplay, likes: .likeCount}'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Upload a video (needs `youtube.upload`)
|
|
108
|
+
|
|
109
|
+
**Confirm with the user before publishing** — show the title, privacy and
|
|
110
|
+
file you're about to upload. Uploads are a two-step *resumable* flow:
|
|
111
|
+
init with metadata → PUT the bytes.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
FILE="/path/to/video.mp4"
|
|
115
|
+
TITLE="My title"
|
|
116
|
+
DESC="My description"
|
|
117
|
+
# privacyStatus: public | unlisted | private
|
|
118
|
+
read -r -d '' META <<JSON
|
|
119
|
+
{"snippet":{"title":"$TITLE","description":"$DESC","categoryId":"22"},
|
|
120
|
+
"status":{"privacyStatus":"unlisted","selfDeclaredMadeForKids":false}}
|
|
121
|
+
JSON
|
|
122
|
+
|
|
123
|
+
# 1. Init resumable session -> capture the upload URL from the Location header.
|
|
124
|
+
UPLOAD_URL=$(curl -sS -D - -o /dev/null \
|
|
125
|
+
-H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
126
|
+
-H "Content-Type: application/json; charset=UTF-8" \
|
|
127
|
+
-H "X-Upload-Content-Type: video/*" \
|
|
128
|
+
-X POST "https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status" \
|
|
129
|
+
-d "$META" | tr -d '\r' | awk '/^[Ll]ocation:/{print $2}')
|
|
130
|
+
|
|
131
|
+
# 2. Upload the bytes -> returns the created video resource (has .id).
|
|
132
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
133
|
+
-H "Content-Type: video/*" \
|
|
134
|
+
-X PUT --upload-file "$FILE" "$UPLOAD_URL" \
|
|
135
|
+
| jq '{id: .id, url: ("https://www.youtube.com/watch?v=" + .id), privacy: .status.privacyStatus}'
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`categoryId` `22` = "People & Blogs" (a safe default). To set a custom
|
|
139
|
+
thumbnail (needs the file to be processed first), call
|
|
140
|
+
`POST /upload/youtube/v3/thumbnails/set?videoId=VIDEO_ID` with the image.
|
|
141
|
+
|
|
142
|
+
## Gotchas
|
|
143
|
+
|
|
144
|
+
- **Quota:** the Data API is quota-metered (default 10,000 units/day). A
|
|
145
|
+
`search` costs 100 units; an `upload` costs ~1,600. A burst of searches
|
|
146
|
+
can exhaust the daily quota → `403 quotaExceeded`; surface it plainly.
|
|
147
|
+
- **No "my videos" endpoint** — always go via the uploads playlist.
|
|
148
|
+
- **`search` results are eventually-consistent** — a freshly uploaded
|
|
149
|
+
video may not appear in `search` for minutes/hours; read it via the
|
|
150
|
+
uploads playlist or by id instead.
|
|
151
|
+
- Uploaded videos start in `uploaded`/`processing` state; stats are `0`
|
|
152
|
+
until processing completes.
|