Claude CodeでZenodoに研究データを公開する前の点検 — メタデータとライセンス
ZenodoのREST APIでドラフトを作り、メタデータ・ファイル・ライセンスを公開前にClaude Codeで点検する手順です。公開操作は権限で人に残します。
公開前に点検するのは、公開すると消せないから
Zenodoは、論文や研究データにDOIを付けて公開できるリポジトリです。REST APIで下書き(deposition)を作り、ファイルを載せ、公開まで進められます。Claude Codeに任せて速くなるのは、その手前の点検です。
理由は単純です。公開済みのdepositionは削除できません。削除できるのは未公開のものだけで、公開済みに対するDELETEは403で返ります。公開時にはDataCiteへDOIが登録されます。作者名の綴り、ライセンス、公開範囲を間違えたまま公開すると、取り消しではなく訂正で対応することになります。
この記事では、Claude Codeに「ドラフトを作る」「メタデータを検査する」「ファイルの整合を見る」までを任せます。公開の操作は、トークンの権限で人の手に残します。出版物の書誌データを同じ発想で点検する例はONIXの書誌情報の点検にあります。
データマネジメントプランの書き方はClaudeでDMPを下書きする手順に譲ります。ここで扱うのは、公開先がZenodoに決まった後の点検です。
公開前点検の流れ
- 1
サンドボックスでドラフトを作る
本番とは別のsandbox.zenodo.orgに、メタデータとファイルを載せます。
- 2
検査スクリプトで機械的に照合する
必須項目、ライセンス、HTMLタグ、チェックサムを、Claudeが読めるJSONで検査します。
- 3
人が画面で確かめて公開する
公開は人が行います。トークンにその権限を持たせません。
Claudeに任せる範囲と、人が握る範囲
点検の作業は、判断が要るものと機械的なものに分かれます。Claudeが得意なのは後者です。
| 作業 | 担当 | 理由 |
|---|---|---|
| ドラフト作成、ファイル投入 | 担当Claude Code | 理由手順が固定で、再実行しても害がない |
| 必須項目・語彙・HTMLタグの検査 | 担当Claude Code | 理由規則が明文化されている |
| ファイルのMD5照合 | 担当Claude Code | 理由数値の一致を見るだけ |
| ライセンスの選定 | 担当人 | 理由共同研究契約や所属機関の規程が絡む |
| 個人情報や機微情報の有無 | 担当人 | 理由データの中身を知っているのは著者 |
| 公開操作 | 担当人 | 理由公開後は削除できない |
ライセンスの選定を人に残す点は、後半の「ライセンスで見落としやすい4点」で理由を示します。契約上の制約は共同研究契約書とMTAの点検手順で扱っています。
サンドボックスとトークンの権限で、公開を構造的に防ぐ
指示文で「公開しないで」と書くだけでは、守られる保証がありません。Zenodoの側と、Claude Codeの側で二重に止めます。
トークンのスコープを絞る
Zenodoのアクセストークンは、スコープで権限が分かれます。deposit:writeはdepositionへの書き込みを許し、公開は許しません。公開・再編集・編集の破棄はdeposit:actionsの担当です。点検用のトークンにはdeposit:writeだけを付けます。
これで、Claudeがうっかり公開のエンドポイントを叩いても、403で止まります。公開の瞬間だけ、人がdeposit:actions付きのトークンを使うか、Web画面から操作します。
サンドボックスは、練習と検査の場として使えます。
- 本番とは別のアカウント登録と、別のアクセストークンが要る
- 発行されるテストDOIは10.5072で始まる(本番は10.5281)
- 環境は予告なく消されることがある
つまりサンドボックスのトークンを渡している間は、本番のデータに触れません。トークンはリクエストのAuthorizationヘッダーで渡します。URLパラメーターでも渡せますが、安全性が下がる方法とされています。
Claude Code側の権限設定
トークンは環境変数に入れてからClaude Codeを起動し、設定ファイルには書きません。次の.claude/settings.jsonは、トークンのファイルと生データをClaudeの読み取りから外し、公開のURLを含むコマンドを止める例です。
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./data/raw/**)",
"Bash(*actions/publish*)"
]
}
}Readのdenyルールは、同じパスへのEditとWriteも止めます。生データを誤って書き換えられる心配も減ります。
ただし、Bashのdenyルールは引数の書き方に弱いと明記されています。URLを変数に分けたり、別の呼び出し方をしたりすると一致しません。actions/publishの行は補助で、本命は先に挙げたトークンのスコープです。また、Claudeはシェルの環境変数を表示させるコマンドも実行できます。点検用のトークンは、漏れても被害が小さいスコープと環境に限ります。
ドラフトを作ってファイルを載せる
まず、メタデータをmetadata.jsonに書きます。POSTのボディはmetadataキーで包みます。例は公開のドキュメントにある書式に沿っています。
{
"metadata": {
"upload_type": "dataset",
"title": "○○調査の回答データ(2025年度)",
"publication_date": "2026-10-01",
"creators": [
{"name": "Yamada, Taro", "affiliation": "○○大学",
"orcid": "0000-0002-1825-0097"}
],
"description": "<p>調査の概要と変数の説明です。</p>",
"access_right": "open",
"license": "cc-by-4.0",
"keywords": ["社会調査", "パネルデータ"],
"version": "1.0.0",
"language": "jpn"
}
}作者名は「姓, 名」の形で書きます。languageはISO 639-2または639-3のコードです。
ドラフトの作成と、ファイルの投入は次の3コマンドです。バケットのURLは、作成時のレスポンスのlinks.bucketに入っています。
export ZENODO_API=https://sandbox.zenodo.org/api
curl -s -X POST "$ZENODO_API/deposit/depositions" \
-H "Authorization: Bearer $ZENODO_TOKEN" \
-H "Content-Type: application/json" \
--data @metadata.json > draft.json
BUCKET=$(jq -r .links.bucket draft.json)
curl -s --upload-file results.csv \
-H "Authorization: Bearer $ZENODO_TOKEN" \
"$BUCKET/results.csv"ファイルのアップロードには、新しいバケット方式を使います。1ファイルあたり、レコード全体で50GBまで、レコード内は100ファイルまでが上限です。旧方式のファイルAPIは、1ファイル100MBまでです。
ここでClaudeに見せるのは、ファイル名とコマンドの出力だけです。results.csvの中身は、curl --upload-fileがClaudeを通さずに送ります。
投入後に、ドラフトをもう一度取得します。これが検査の入力になります。
ID=$(jq -r .id draft.json)
curl -s -H "Authorization: Bearer $ZENODO_TOKEN" \
"$ZENODO_API/deposit/depositions/$ID" > draft.jsonメタデータの検査を、スクリプトにしておく
Claudeに「メタデータを見て」と頼むと、読むたびに観点がぶれます。規則はスクリプトに固定し、Claudeには実行と結果の読み解きを任せます。次のcheck_zenodo_draft.pyは、ドキュメントの必須条件と語彙をそのまま検査にしたものです。
import json, re, sys, hashlib
from html.parser import HTMLParser
ALLOWED = set("""a abbr acronym b blockquote br code caption div em i
li ol p pre span strike strong sub table tbody thead th td tr u ul""".split())
RELATIONS = set("""isCitedBy cites isSupplementTo isSupplementedBy
isContinuedBy continues isDescribedBy describes hasMetadata isMetadataFor
isNewVersionOf isPreviousVersionOf isPartOf hasPart isReferencedBy
references isDocumentedBy documents isCompiledBy compiles isVariantFormOf
isOriginalFormof isIdenticalTo isAlternateIdentifier isReviewedBy reviews
isDerivedFrom isSourceOf requires isRequiredBy isObsoletedBy
obsoletes""".split())
REQUIRED = ["upload_type", "publication_date", "title", "creators",
"description", "access_right"]
class Tags(HTMLParser):
def __init__(self):
super().__init__(); self.bad = set()
def handle_starttag(self, tag, attrs):
if tag not in ALLOWED: self.bad.add(tag)
d = json.load(open(sys.argv[1]))
m = d.get("metadata", {})
out = []
for k in REQUIRED:
if not m.get(k): out.append(f"必須項目がありません: {k}")
ar = m.get("access_right", "open")
if ar in ("open", "embargoed") and not m.get("license"):
out.append("license 未指定(既定値が入ります)")
if ar == "embargoed" and not m.get("embargo_date"):
out.append("embargo_date がありません")
if ar == "restricted" and not m.get("access_conditions"):
out.append("access_conditions がありません")
for c in m.get("creators", []):
if "," not in c.get("name", ""):
out.append(f"作者名が「姓, 名」形式でありません: {c.get('name')}")
o = c.get("orcid")
if o and not re.fullmatch(r"\d{4}-\d{4}-\d{4}-\d{3}[\dX]", o):
out.append(f"ORCID の形式が不正です: {o}")
p = Tags(); p.feed(m.get("description", "")); p.feed(m.get("notes", ""))
if p.bad: out.append(f"使えない HTML タグ: {sorted(p.bad)}")
for r in m.get("related_identifiers", []):
if r.get("relation") not in RELATIONS:
out.append(f"relation が語彙にありません: {r.get('relation')}")
for f in d.get("files", []):
md5 = hashlib.md5(open(f["filename"], "rb").read()).hexdigest()
if md5 != f["checksum"].removeprefix("md5:"):
out.append(f"チェックサム不一致: {f['filename']}")
print("\n".join(out) or "指摘なし")ファイルは、スクリプトと同じディレクトリに置く前提です。作者名の検査は、カンマの有無しか見ません。姓名の順序の誤りは見つけられません。ORCIDの検査も形式だけで、実在までは確かめません。スクリプトが「指摘なし」でも、人の目で見る項目は残ります。
python check_zenodo_draft.py draft.json出力はそのままClaudeに読ませ、指摘ごとにmetadata.jsonの修正案を出させます。修正したら、PUTでメタデータを更新し、取得し直して再検査します。この反復で、指摘が0件になるまで回します。
Zenodoが検証エラーを返す場合は、400とerrorsの配列が付きます。各要素のfieldがmetadata.creators.0.nameのように場所を指すので、これも貼れば直す場所が定まります。
ライセンスで見落としやすい4点
1. 指定しないと、既定値が入る
licenseを省くと、データセットにはcc-zero、それ以外にはcc-byが入ります。「入れ忘れ」が、意図しないCC0での公開になりえます。スクリプトが未指定を指摘するのは、このためです。
2. ライセンスは、depositionの全ファイルに1つだけ
選んだライセンスは、そのdepositionのすべてのファイルに適用されます。メタデータそのものはCC0で公開されます。解析コードをMIT、データをCC BY 4.0にしたい場合は、depositionを分ける必要があります。
3. ライセンスIDは、その場で引いて確かめる
ドキュメントは「利用できるIDは/api/licensesで探せる」と書き、例にcc-by-nc-4.0を挙げています。一方、このエンドポイントはドキュメントの冒頭で「テスト中でベータ前」とも位置づけられています。
実際に問い合わせると、食い違いが見つかります。/api/licenses/?q=cc-byは/api/vocabularies/licensesへリダイレクトされます。/api/licenses/cc-by-4.0と/api/licenses/cc-by-nc-4.0は404でした。/api/vocabularies/licenses/cc-by-4.0とcc0-1.0は200で返り、cc-zeroというIDは404でした。ドキュメントの既定値の表記と、現行の語彙のIDが一致していません。
そこで、IDは記憶やドキュメントの例から書かず、下のコマンドで引きます。
curl -sL "https://zenodo.org/api/vocabularies/licenses/cc-by-4.0" \
| jq '{id, url: .props.url}'この現象は、CLAUDE.mdに「ライセンスIDは語彙のエンドポイントで引いてから書く」と1行入れておけば、Claudeが毎回その手順を踏みます。
4. 契約と規程との整合は、人が決める
共同研究契約、MTA、所属機関のデータポリシーは、ライセンスを制約することがあります。Claudeに契約書を読ませて「CC BYで問題ない」と結論させても、判断の責任は著者に残ります。契約の条項を一覧にし、公開との衝突がありそうな箇所を列挙させるところまでにとどめます。
OSSのコードをデータと一緒に載せる場合は、依存ライブラリのライセンスも関わります。一覧化の方法はOSSライセンスの点検手順にあります。
公開範囲・DOI・助成金の点検
公開範囲の組み合わせ
access_rightには4つの値があり、必須項目が変わります。
| access_right | 意味 | 追加で必要なもの |
|---|---|---|
| open | 意味公開 | 追加で必要なものlicense |
| embargoed | 意味期日まで非公開 | 追加で必要なものlicense、embargo_date |
| restricted | 意味申請制 | 追加で必要なものaccess_conditions |
| closed | 意味非公開 | 追加で必要なものなし |
restrictedのアクセス条件は、利用者の申請理由に基づいて著者が許可を判断する仕組みです。アクセスに対して課金することは認められていません。
DOIの事前予約
READMEや論文にDOIを書く必要があるときは、prereserve_doiをtrueにします。公開前に予約DOIが返ります。このDOIは変更できず、DataCiteへの登録は公開するまで行われません。つまり、予約したDOIをREADMEに書いておき、公開後に実際に引けることを確かめる、という順序になります。サンドボックスの予約DOIは10.5072で始まるので、本番のDOIとは別物です。
助成金の欄は、日本の機関が載っていない
grantsは、OpenAIREが対応する助成金を、配分機関のDOI接頭辞付きのIDで指定する欄です。ドキュメントに挙がる配分機関は、European Commission、NSF、NIH、UKRIなどです。科研費(JSPS)やAMEDの接頭辞は、この一覧に見当たりません。
日本の助成金を受けた研究は、notesに謝辞として課題番号を書く運用が現実的です。notesで使えるHTMLタグは、descriptionと同じ制限です。
公開の前に、人が画面で見るもの
検査が通ったら、ドラフトのHTML画面を人が開きます。取得したJSONのlinks.htmlが、その画面のURLです。
見るのは、機械が判定できない項目です。
- 作者の順序と所属の表記が、論文と合っているか
- 説明文の変数名や単位に、誤りがないか
- 同梱ファイルに、個人情報や共有できない試料の情報が混じっていないか
- 選んだライセンスと公開範囲が、契約と規程に合っているか
すべてに納得できたら、人が公開します。公開のエンドポイントは202を返し、非同期で処理されます。
公開した後でメタデータを直す場合は、actions/editで編集を解除します。ただし、編集の途中は409を返すことがあり、統合の完了まで5分ほど待つ必要があります。内容を変えたデータは、actions/newversionで新版を作ります。このとき指定するIDは、最新版のIDです。全版をまとめるグローバルIDでは作れません。レスポンスの本体は元のレコードで、新しい下書きはlinks.latest_draftから辿ります。
ソフトウェアはGitHub連携も選べる
解析コードなどのソフトウェアは、GitHubのリリースと連携して公開する方法もあります。リポジトリの直下に.zenodo.jsonを置けば、作者・ライセンス・キーワード・関連識別子を指定できます。書式はdepositのメタデータと同じです。
この場合もClaudeに検査させられます。licenseやcreatorsが埋まっているか、JSONとして壊れていないかを、リリースを切る前に読ませます。リリースを切れば公開が走るので、タグを打つ操作は人に残します。
CLAUDE.mdに入れる規約
点検の作法は、プロジェクトのCLAUDE.mdに書いておくと、セッションをまたいでも守られます。
## Zenodo 公開前点検
- API は https://sandbox.zenodo.org/api のみ呼ぶ。本番は呼ばない
- actions/publish を呼ばない。公開は人が行う
- data/raw/ は読まない。扱うのはファイル名・サイズ・MD5 だけ
- ライセンス ID は /api/vocabularies/licenses/<id> で引いてから書く
- 点検は check_zenodo_draft.py の出力を根拠にし、ライセンスは選ばない落とし穴
サンドボックスで通ったのに、本番で別のアカウントが要る
サンドボックスと本番は、アカウントもトークンも別です。別の環境なので、depositionもテストDOIも別物です。本番で公開する前に、本番側でもドラフトを作り直して検査を回します。メタデータのJSONとスクリプトは、そのまま使い回せます。
連続して大量のファイルやdepositionを検査するときは、リクエストの回数に注意します。認証済みのユーザーは、1分100回、1時間5000回までです。超えると429が返ります。レスポンスヘッダーのX-RateLimit-Remainingで残りを確かめられます。
まとめ
Zenodoの公開は取り消せないため、公開の前に点検の時間を取る価値があります。Claude Codeには、ドラフトの作成、規則に沿った検査、チェックサムの照合を任せます。ライセンスの選定、契約との整合、公開の操作は、人の仕事として残します。
その線引きを、指示文ではなくトークンのスコープで作るのが要点です。deposit:writeだけのトークンなら、Claudeが公開できる経路自体がありません。