Currently under development, and there may be breaking changes.
zgsync is a command-line tool that posts help center content written in Markdown via the Zendesk Help Center REST API.
When posting, it converts the Markdown to HTML to match the API interface.
brew install tukaelu/tap/zgsync
Please download the appropriate Zip archive for your environment from the releases.
By default, it references the configuration file at ~/.config/zgsync/config.yaml, so please create it in advance.
You can also explicitly specify the path using the --config option.
subdomain: <your zendesk subdomain>
email: <your zendesk email address>/token
token: <your zendesk token>
default_comments_disabled: true
default_locale: ja
default_permission_group_id: 123
default_user_segment_id: 456
notify_subscribers: false
contents_dir: path/to/contents
enable_link_target_blank: false| Key | Required | Description |
|---|---|---|
| subdomain | true | Specify a brand-specific subdomain |
| auth_type | false | Authentication type: token (default), oauth, or client_credentials |
| (1) | Specify the email address with "/token" added to the end | |
| token | (1) | Specify your API token |
| oauth_client_id | (2) | OAuth client ID (identifier) (env: ZGSYNC_OAUTH_CLIENT_ID) |
| oauth_scope | false | OAuth scope (default: hc:read hc:write) |
| default_comments_disabled | false | Specify the default comments disabled |
| default_locale | true | Specify the default locale for translations |
| default_permission_group_id | true | Specify the default permission group ID |
| default_user_segment_id | false | Specify the default user segment ID |
| notify_subscribers | false | Specify whether to notify subscribers of the article |
| contents_dir | false | Specify the local directory path to manage articles |
| enable_link_target_blank | false | Specify if links open in a new tab (affected only push) |
- (1) Required when
auth_typeistoken. - (2) Required when
auth_typeisoauthorclient_credentials.
The OAuth client secret (required for auth_type: client_credentials) is only accepted via the ZGSYNC_OAUTH_CLIENT_SECRET environment variable so that it never has to be written to a file.
Warning
Zendesk is deprecating API tokens: new token creation will be blocked on October 27, 2026, and all tokens will stop working on April 30, 2027. Migrate to OAuth before then. The token auth type remains supported until the deprecation for backward compatibility.
Create an OAuth client in the Zendesk Admin Center (Apps and integrations > APIs > OAuth clients) with:
- Client kind: Public
- Redirect URLs:
http://localhost:8976/callback
Then configure zgsync and log in:
subdomain: <your zendesk subdomain>
auth_type: oauth
oauth_client_id: <your oauth client identifier>
# ... other settingszgsync auth login
This opens your browser to authorize zgsync (authorization code grant with PKCE; no client secret is needed).
Tokens are saved to ~/.config/zgsync/credentials.json and refreshed automatically.
When the refresh token expires, run zgsync auth login again.
Create a separate OAuth client with Client kind: Confidential and save its secret in your CI secrets. The client credentials grant obtains a short-lived access token at runtime, so no token needs to be stored.
subdomain: <your zendesk subdomain>
auth_type: client_credentials
oauth_client_id: <your oauth client identifier>
# ... other settingsThe client secret is passed via the ZGSYNC_OAUTH_CLIENT_SECRET environment variable (it is not read from the config file). The client ID can also be overridden with ZGSYNC_OAUTH_CLIENT_ID.
Example GitHub Actions workflow:
- name: Push articles to Zendesk
env:
ZGSYNC_OAUTH_CLIENT_ID: ${{ secrets.ZENDESK_OAUTH_CLIENT_ID }}
ZGSYNC_OAUTH_CLIENT_SECRET: ${{ secrets.ZENDESK_OAUTH_CLIENT_SECRET }}
run: zgsync push --config ./zgsync.yaml docs/**/*.mdNote
With the client credentials grant, API requests are performed as the owner of the OAuth client. Articles created or updated from CI are attributed to that user.
zgsync consists of the subcommands pull, push, empty, archive, and auth. By default, it handles Translations among the data models of the Zendesk Help Center, but it can also handle Articles by specifying a specific option.
zgsync saves Translations in files named {Article ID}-{Locale}.md. When using the pull or empty commands, specifying the --save-article option saves Articles in files named {Article ID}.md.
When pushing, it does not automatically determine whether it is a Translation or an Article. Therefore, to post an Article, explicitly specify the --article option and provide the Article file.
The push subcommand updates posts, either Translations or Articles, to the remote.
Usage: zgsync push <files> ... [flags]
Push translations or articles to the remote.
Arguments:
<files> ... Specify the files to push.
Flags:
--article Specify when posting an article. If not specified, the translation will be pushed.
--dry-run dry run
--raw It pushes raw data without converting it from Markdown to HTML.
The pull subcommand retrieves translations or articles from the remote and saves them locally.
Usage: zgsync pull <article-i-ds> ... [flags]
Pull translations or articles from the remote.
Arguments:
<article-i-ds> ... Specify the article IDs to pull.
Flags:
-l, --locale=STRING Specify the locale to pull. If not specified, the default locale will be used.
--raw It pulls raw data without converting it from HTML to Markdown.
-a, --save-article It pulls and saves the article in addition to the translation.
-S, --with-section-dir A .md file will be created in the section ID directory.
By default, the pull subcommand saves under {contents_dir}. You can also specify an option to output directly under {contents_dir}/{section_id}.
If a Translation or Article already exists at the specified local path, it will be overwritten.
The empty subcommand creates an empty draft article remotely and saves it locally.
Usage: zgsync empty --section-id=INT --title=STRING [flags]
Creates an empty draft article remotely and saves it locally.
Flags:
-s, --section-id=INT Specify the section ID of the article.
-t, --title=STRING Specify the title of the article.
-l, --locale=STRING Specify the locale to pull. If not specified, the default locale will be used.
-p, --permission-group-id=INT Specify the permission group ID. If not specified, the default value will be used.
-u, --user-segment-id=INT Specify the user segment ID. If not specified, the default value will be used.
--save-article It saves the article in addition to the translation.
-S, --with-section-dir A .md file will be created in the section ID directory.
The empty subcommand should not be used when adding a new Translation to an existing Article.
The archive subcommand archives an article on the remote. It accepts either an article ID or a local article file path as the target.
Usage: zgsync archive <target> [flags]
Archives an article on the remote.
Arguments:
<target> Specify the article ID or file path of the article to archive.
The auth login subcommand logs in to Zendesk via OAuth and saves the tokens to ~/.config/zgsync/credentials.json. See Authentication for the setup.
Usage: zgsync auth login [flags]
Log in to Zendesk via OAuth (authorization code grant with PKCE) and save the tokens locally.
Flags:
--port=8976 Port of the local callback server. The OAuth client must register
http://localhost:<port>/callback as a redirect URL.
--bind="localhost" Bind address of the local callback server (e.g. 0.0.0.0 for Docker).
zgsync manages Translations and Articles in the following formats respectively.
Translations are files composed of Frontmatter and Markdown text. The Markdown, which corresponds to the body of the article, is written in this file.
Ensure that the Markdown Frontmatter related to properties required by the API is not missing.
The section_id is included for administrative purposes but is not required by the Translation API.
---
title: cool title
locale: ja
draft: true
outdated: false
section_id: 1234567890
source_id: 12345678901234
html_url: https://{your help center domain}/hc/ja/articles/12345678901234
---
## Markdown
some cool textrefs: Translations | Zendesk Developer Docs
Articles manage only the metadata related to the post in the Frontmatter. Please note that any body text written in this file will be ignored.
---
author_id: 98765432109876
comments_disabled: true
content_tag_ids: []
created_at: "2024-01-01T00:00:00Z"
draft: false
edited_at: "2024-01-01T00:00:00Z"
html_url: https://{your help center domain}/hc/ja/articles/12345678901234
id: 12345678901234
label_names: []
locale: ja
outdated: false
outdated_locales: []
permission_group_id: 1234567
position: 0
promoted: false
section_id: 567890123456
source_locale: ja
title: cool title
updated_at: "2024-01-01T00:00:00Z"
url: https://{subdomain}.zendesk.com/api/v2/help_center/ja/articles/12345678901234.json
user_segment_id: 234567890123
user_segment_ids: []
vote_count: 0
vote_sum: 0
---refs: Articles | Zendesk Developer Docs
- The conversion from Markdown to HTML is performed by yuin/goldmark, which adheres to the CommonMark specification.
- It supports the output of div tags using notation similar to Pandoc.
:::
messages
:::- For headings such as h1 and h2 tags, as well as div tags, it also supports the specification of attributes.
## Hoge {#hoge .h2} // ==> <h2 id="hoge" class="h2">Hoge</h2>
:::{.block .warning} // ==> <div class="block warning"><p>warning messages</p></div>
warning messages
:::- The conversion from HTML to Markdown uses JohannesKaufmann/html-to-markdown, so fully consistent bidirectional conversion is not currently supported.
- If
enable_link_target_blankis set totrue, thetarget="_blank" rel="noopener noreferrer"attributes will be added to all anchor () tags.
- Go 1.25.0 or later
- GNU Make
- golangci-lint (for linting)
For an enhanced development experience, we recommend using Claude Code with the SERENA MCP server for intelligent code assistance.
After cloning the repository, set up SERENA by running:
# Add SERENA MCP server to Claude Code
claude mcp add serena -- uvx --from git+https://github.com/oraios/serena serena-mcp-server --context ide-assistant --project $(pwd)Then, initialize SERENA in Claude Code:
/mcp__serena__initial_instructions
Note: SERENA memory files are not tracked in Git and should be initialized in each development environment.
# Build the binary
make build
# Run tests
make test
# Run linter
make lint
# Clean build artifacts
make cleanContributions are very welcome! Feel free to submit issues and pull requests.
MIT License
Copyright (c) 2024 Tsukasa NISHIYAMA