ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English
App Store審査リジェクト後の修正・再提出をApp Store Connect APIで実行する

App Store審査リジェクト後の修正・再提出をApp Store Connect APIで実行する

App Store Connect の審査でリジェクトされた後、修正からの再提出までの全工程を API で実行しました。ブラウザでの操作は一切行っていません。 リジェクトの内容 JPS Explorer(ジャパンサーチ文化資源探索アプリ)の初回提出で、2つの問題を指摘されました。 チップ(Tip Jar)画面でエラーが表示される — In-App Purchase の商品が App Store Connect に未登録だったため カメラ検索の「撮影」ボタンでクラッシュ — iOS の Info.plist に NSCameraUsageDescription が未設定だったため 修正内容 カメラクラッシュの修正 Info.plist にカメラと写真ライブラリの権限記述を追加しました。 <key>NSCameraUsageDescription</key> <string>文化資源に似た画像を検索するためにカメラを使用します</string> <key>NSPhotoLibraryUsageDescription</key> <string>文化資源に似た画像を検索するために写真ライブラリを使用します</string> Flutter の image_picker パッケージを使ってカメラにアクセスする場合、この記述がないと実機でクラッシュします。シミュレータではカメラが使えないため、この問題には気づきにくいようです。 合わせて、PlatformException の camera_access_denied と photo_access_denied のハンドリングも追加しました。 チップ画面の修正 In-App Purchase の商品が未登録の場合、StoreKit がエラーを返します。エラーをそのまま表示するのではなく、「準備中です」というメッセージに変更しました。 API による再提出の手順 リジェクト後の状態では、App Store Connect のブラウザに「編集」ボタンと「App Reviewに再提出」ボタンが表示されます。これらの操作はすべて API で実行できます。 Step 1: ビルド番号を上げてアップロード リジェクトされたビルドと同じビルド番号では再アップロードできないため、pubspec.yaml のビルド番号を上げます。 # 変更前 version: 1.0.0+1 # 変更後 version: 1.0.0+2 ビルドしてアップロードします。 ...

Apple Sales Reports APIのデータ反映時刻とYouTube APIのクォータリセットを実測した

外部APIを使って日次のデータ取得を自動化する場合、「いつデータが利用可能になるか」「いつクォータがリセットされるか」を把握しておくとスケジューリングに役立ちます。この記事では、Apple App Store Connect Sales Reports APIとYouTube Data API v3について、実際に観測したタイミングを記録します。 Apple App Store Connect Sales Reports API 公式ドキュメントの記載 Appleの公式ドキュメントでは、日次の売上レポートは翌日の太平洋時間 午前8時まで(by 8:00 AM Pacific Time)に利用可能になるとされています。 参照: Sales and Trends Reports Availability - Apple Developer 実測結果 2026年3月22日に、前日(2026年3月21日)分のデータがいつ取得可能になるかを1時間ごとに確認しました。 16:00 JST (0:00 PT) → 取得不可 17:00 JST (1:00 PT) → 取得不可 18:00 JST (2:00 PT) → 取得不可 19:00 JST (3:00 PT) → 取得不可 20:00 JST (4:00 PT) → 取得不可 21:09 JST (5:09 PT) → 取得可能 今回の計測では、太平洋時間の午前5時頃(日本時間 21時頃)にデータが利用可能になっていました。公式に記載されている午前8時よりも約3時間早いタイミングです。 ...

ジャパンサーチの類似画像検索APIの内部構造

ジャパンサーチの類似画像検索APIの内部構造

ジャパンサーチ(https://jpsearch.go.jp)の「画像AI検索」機能は、テキストによるモチーフ検索と、画像をアップロードしての類似画像検索を提供しています。公式の簡易Web APIガイドにはテキスト検索(text2image)と既存アイテムIDによる類似検索(imageパラメータ)の説明がありますが、画像アップロードによる検索については記載がありません。 Web UIのネットワーク通信を調査したところ、画像アップロード検索は3段階のAPIで実現されているようです。 APIの3段階フロー Step 1: 画像から特徴量ベクトルを抽出 エンドポイント: POST https://jpsearch.go.jp/dl/api/imagefeatures/ 画像をBase64エンコードして送信すると、64次元の特徴量ベクトルが返ります。 リクエスト: { "img_b64": "data:image/jpeg;base64,/9j/4AAQ..." } レスポンス: { "body": [-0.0879, -0.0091, -0.0712, ...(64次元)] } Step 2: 特徴量ベクトルから一時的な検索IDを生成 エンドポイント: POST https://jpsearch.go.jp/api/item/create-image-feature Step 1で取得した特徴量ベクトル(配列)をそのままPOSTすると、一時的なIDが文字列で返ります。 リクエスト: [-0.0879, -0.0091, -0.0712, ...] レスポンス(プレーンテキスト): jpsxt-Wnl3p6dJJ8b このリクエストには X-Requested-With: XmlHttpRequest ヘッダーが必要です。 Step 3: IDで類似画像検索を実行 エンドポイント: GET https://jpsearch.go.jp/api/item/search/jps-cross?image={ID}&size=20 Step 2で取得したIDを、通常の類似画像検索と同じ image パラメータに渡します。このIDはJPSの既存アイテムIDと同じ形式で扱われるため、通常の検索APIがそのまま使えます。 レスポンスは通常のアイテム検索と同じJSON形式です。 検証結果 アプリのアイコン画像(虫眼鏡のイラスト)で検索したところ、1,932件がヒットし、民博の「肩掛袋」「上衣」「腰帯」などテキスタイル系の資料が上位に返りました。色味や形状の特徴で類似度が計算されていると考えられます。 ヘッダーの要件 調査の過程で、いくつかのヘッダーが必要であることがわかりました。 ヘッダー Step 1 Step 2 Step 3 Content-Type: application/json 必要 必要 不要 Origin: https://jpsearch.go.jp 必要 必要 不要 X-Requested-With: XmlHttpRequest 不要 必要 不要 Step 1とStep 2はPOST、Step 3はGETです。 ...

App Store Connect APIでiOSアプリのアップデートを審査提出する方法

App Store Connect APIでiOSアプリのアップデートを審査提出する方法

TL;DR iOSアプリのアップデート版を ビルド → アップロード → ビルド紐付け → whatsNew設定 → 審査提出 まで、すべてコマンドラインとApp Store Connect REST APIで完結させた。初回リリース時と異なり、メタデータやスクリーンショットは既存のものが引き継がれるため、更新時に必要な操作は少ない。 前提: App Store Connect APIだけでiOSアプリを審査提出する完全ガイドのセットアップ(APIキー取得・JWT生成・ヘルパー関数)が完了しているものとする。 全体の流れ ビルド番号のインクリメント アーカイブ・IPA書き出し・アップロード(xcodebuild + xcrun altool) ビルドの処理完了を確認(API) ビルドをバージョンに紐付け(API) 暗号化コンプライアンスの設定(API) whatsNew(新機能)の設定(API) 審査提出(API) 1. ビルド番号のインクリメント App Store Connectは同じビルド番号のアップロードを拒否する。CURRENT_PROJECT_VERSION を上げる必要がある。 XcodeGenを使っている場合は project.yml を編集する: # project.yml settings: base: MARKETING_VERSION: "1.1.0" CURRENT_PROJECT_VERSION: "4" # 3 → 4 に変更 ポイント: マーケティングバージョン(1.1.0)はユーザーに見えるバージョン番号、ビルド番号(4)は同一バージョン内の連番。不具合修正の再提出など、ユーザーから見た変更がない場合はビルド番号だけ上げればよい。 2. アーカイブ・アップロード # 環境変数を設定 export APP_STORE_API_KEY="YOUR_KEY_ID" export APP_STORE_API_ISSUER="YOUR_ISSUER_ID" # XcodeGenでプロジェクト再生成 xcodegen generate # アーカイブ xcodebuild archive \ -project KotenOCR.xcodeproj \ -scheme KotenOCR \ -archivePath build/KotenOCR.xcarchive \ -destination "generic/platform=iOS" \ -quiet # IPA書き出し xcodebuild -exportArchive \ -archivePath build/KotenOCR.xcarchive \ -exportPath build/export \ -exportOptionsPlist scripts/ExportOptions.plist \ -quiet # App Store Connectへアップロード xcrun altool --upload-app \ --type ios \ --file build/export/KotenOCR.ipa \ --apiKey "$APP_STORE_API_KEY" \ --apiIssuer "$APP_STORE_API_ISSUER" 注意: xcrun altool に渡す環境変数は export しておく必要がある。source .env だけではサブプロセスに渡らない。 ...

App Store Connect APIでiOSアプリにチップ(Tip Jar)機能を追加する完全ガイド

App Store Connect APIでiOSアプリにチップ(Tip Jar)機能を追加する完全ガイド

TL;DR iOSアプリにチップ(Tip Jar)機能を追加した。SwiftUI + StoreKit 2 でアプリ側を実装し、App Store Connect REST API を使って商品登録・ローカライズ・価格設定・審査用スクリーンショット・配信地域設定・TestFlight配信までをコマンドラインから完了させた。本記事ではその全手順を再現可能な形で記載する。 前提: App Store Connect APIだけでiOSアプリを審査提出する完全ガイドの続編として、APIキーの取得・JWT生成は既にセットアップ済みとする。 全体の流れ アプリ側の実装(StoreKit 2 + SwiftUI) App Store Connect APIで商品登録(3つの消費型アイテム) ローカライズ設定(日本語・英語) 価格設定($0.99 / $2.99 / $6.99) 審査用スクリーンショットのアップロード 配信地域の設定 有料アプリ契約の締結 TestFlightでの動作確認 1. アプリ側の実装 1.1 StoreKit設定ファイル Xcodeのテスト環境用に TipJar.storekit を作成する。これによりシミュレータでStoreKitのテストが可能になる。 { "products" : [ { "displayPrice" : "0.99", "familyShareable" : false, "internalID" : "tip_small_001", "localizations" : [ { "description" : "開発を応援する小さなチップ", "displayName" : "小さな応援", "locale" : "ja" }, { "description" : "A small tip to support development", "displayName" : "Small Tip", "locale" : "en_US" } ], "productID" : "com.example.app.tip.small", "referenceName" : "Small Tip", "type" : "Consumable" } ] } XcodeGen を使っている場合は project.yml の scheme に StoreKit 設定を追加する: ...

DTS (Distributed Text Services) 1.0 正式リリースへの対応 ― TEI/XMLテキストAPIの仕様更新記録

はじめに 2026年2月、テキストコレクションへの標準的なアクセス手段を提供するAPI仕様「Distributed Text Services (DTS)」のv1.0が正式にリリースされました。 本記事では、DTS 1-alpha に基づいて構築していた校異源氏物語テキストDB用APIを、DTS 1.0に対応させた際の変更点を整理します。 https://github.com/distributed-text-services/specifications/releases/tag/v1.0 DTS とは DTS は、TEI/XMLなどのテキストコレクションに対する標準的なアクセスAPIを定める仕様です。以下の4つのエンドポイントで構成されます。 エンドポイント 役割 Entry Point APIの各エンドポイントURLを返す Collection テキスト間のナビゲーション(コレクション・リソースの一覧) Navigation テキスト内のナビゲーション(引用構造の探索) Document テキスト本体の取得(TEI/XMLの全体または一部) 対象プロジェクト 校異源氏物語テキストDBのDTS API実装(TypeScript/Express.js)です。 本番: https://dts-typescript.vercel.app/api/v2/dts ソースコード: https://github.com/nakamura196/dts-typescript 1-alpha から 1.0 への主な変更点 1. JSON-LD Context URLの変更 最も基本的な変更は、JSON-LDコンテキストファイルのURLです。 - "@context": "https://distributed-text-services.github.io/specifications/context/1-alpha1.json" + "@context": "https://dtsapi.org/context/v1.0.json" ドメインが distributed-text-services.github.io から dtsapi.org に変わりました。これは仕様の正式公開に伴い、永続的なURLが割り当てられたことを意味します。 2. dtsVersion の更新 - "dtsVersion": "1-alpha" + "dtsVersion": "1.0" 全エンドポイントのレスポンスに含まれる dtsVersion フィールドを更新します。 3. URI Template の全パラメータ記載 1-alphaでは一部のパラメータのみ記載していましたが、1.0ではAPIが受け付ける全パラメータをURI templateに含める必要があります。 Entry Point のレスポンス例: ...

App Store Connect APIだけでiOSアプリを審査提出する手順

App Store Connect APIだけでiOSアプリを審査提出する手順

TL;DR App Store Connect の REST API を使い、コマンドラインからiOSアプリの審査提出に必要なほぼ全作業(メタデータ・スクリーンショット・年齢レーティング・ビルド紐付け・URL設定・暗号化コンプライアンス・価格設定)を完了させた。本記事ではその手順を再現可能な形で記載する。 注意: 「App Privacy(アプリのプライバシー)」のデータ使用状況の宣言だけは、2026年3月時点でAPIが提供されておらず、App Store Connectのブラウザから設定する必要がある。 前提条件 Apple Developer Program に登録済み App Store Connect で API キーを発行済み アプリの Bundle ID が登録済み Xcode でアーカイブ・アップロード済みのビルドが存在する(xcodebuild -exportArchive でアップロード可能) Python 3 + PyJWT + cryptography がインストール済み pip install PyJWT cryptography 1. API キーの準備 1.1 API キーの発行 App Store Connect → ユーザーとアクセス → 統合 → App Store Connect API から新しいキーを発行する。 名前: 任意(例: deploy-key) アクセス: Admin(メタデータ更新・提出に必要) 発行後、以下の情報をメモする: 項目 説明 Key ID APIキーの識別子(10文字程度の英数字) Issuer ID 組織の識別子(UUID形式) ダウンロードした .p8 ファイルは安全な場所に保存する(一度しかダウンロードできない): ...

はてなブログの記事を一括で非公開にする方法(AtomPub API)

はてなブログの記事を一括で非公開にする方法(AtomPub API)

はてなブログの記事を別サイトに移行した後、旧記事を一括で非公開にしたいケースがあります。 注意点:下書きには戻せない はてなブログのAtomPub APIでは、公開済みの記事を下書き(draft)に戻すことはできません。PUTリクエストで <app:draft>yes</app:draft> を送ると 400 Cannot Change into Draft エラーになります。 そのため、以下の2つの方法があります。 方法1:記事本文を「移転しました」に書き換える AtomPub APIのPUTで記事の <content> を書き換えることは可能です。 import requests import xml.etree.ElementTree as ET import time HATENA_ID = "your_hatena_id" BLOG_ID = "your_blog_id.hatenablog.com" API_KEY = "your_api_key" NEW_SITE_URL = "https://your-new-site.com" ATOM_NS = "http://www.w3.org/2005/Atom" def fetch_all_entries(): entries = [] url = f"https://blog.hatena.ne.jp/{HATENA_ID}/{BLOG_ID}/atom/entry" while url: resp = requests.get(url, auth=(HATENA_ID, API_KEY), timeout=30) resp.raise_for_status() root = ET.fromstring(resp.text) for entry in root.findall(f"{{{ATOM_NS}}}entry"): title_el = entry.find(f"{{{ATOM_NS}}}title") title = title_el.text or "" if title_el is not None else "" edit_link = entry.find(f"{{{ATOM_NS}}}link[@rel='edit']") edit_url = edit_link.get("href") if edit_link is not None else None if edit_url: entries.append({"title": title, "edit_url": edit_url}) next_el = root.find(f"{{{ATOM_NS}}}link[@rel='next']") url = next_el.get("href") if next_el is not None else None return entries def replace_content(entry): title = entry["title"] update_xml = f"""<?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app"> <title>{title}</title> <content type="text/plain">この記事は {NEW_SITE_URL} に移転しました。</content> </entry>""" resp = requests.put( entry["edit_url"], auth=(HATENA_ID, API_KEY), data=update_xml.encode("utf-8"), headers={"Content-Type": "application/atom+xml; charset=utf-8"}, timeout=30, ) return resp.status_code entries = fetch_all_entries() print(f"Found {len(entries)} entries") for i, e in enumerate(entries): status = replace_content(e) print(f"[{i+1}/{len(entries)}] {status}: {e['title'][:50]}") time.sleep(0.5) 方法2:はてなブログの管理画面から一括削除 記事数が少なければ、管理画面の「記事の管理」から手動で削除する方法もあります。ただし一括選択機能がないため、大量の記事には向きません。 ...

Hypothes.is APIでWebアノテーションをエクスポートしてTEI/XMLに変換する

Hypothes.is APIでWebアノテーションをエクスポートしてTEI/XMLに変換する

はじめに Hypothes.isは、Webページ上にハイライトやコメントを付けられるオープンソースのアノテーションツールです。ブラウザ拡張やJavaScriptの埋め込みで手軽に使えますが、蓄積したアノテーションをバックアップしたい、あるいはTEI/XMLなど別の形式で活用したいケースもあります。 本記事では、Hypothes.is APIを使ってアノテーションをエクスポートし、TEI/XMLに変換する方法を紹介します。 APIキーの取得 Hypothes.isにログイン Developer settings にアクセス 「Generate your API token」でAPIキーを生成 取得したキーを.envファイルに保存します。 cp .env.example .env # .env を編集してAPIキーを設定 HYPOTHESIS_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx アノテーションのエクスポート APIの基本 Hypothes.is APIのベースURLは https://api.hypothes.is/api です。認証はAuthorization: Bearer <API_KEY>ヘッダーで行います。 主要なエンドポイント: エンドポイント 用途 GET /api/profile 認証ユーザーのプロフィール取得 GET /api/search アノテーション検索 GET /api/annotations/{id} 個別アノテーション取得 スクリプト エクスポートからTEI/XML変換までを1つのスクリプト hypothes_export.py にまとめています。 https://github.com/nakamura196/hypothes-export/blob/main/hypothes_export.py 以下、主要な処理を抜粋して説明します。 .envの読み込みとAPI呼び出し def load_env(): env_path = Path(__file__).parent / ".env" with open(env_path) as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) os.environ[k.strip()] = v.strip() def api_get(endpoint, params=None): api_key = os.environ["HYPOTHESIS_API_KEY"] url = f"https://api.hypothes.is/api/{endpoint}" if params: url += "?" + urllib.parse.urlencode(params) req = urllib.request.Request(url) req.add_header("Authorization", f"Bearer {api_key}") with urllib.request.urlopen(req) as resp: return json.loads(resp.read().decode()) 全アノテーションの取得(ページネーション対応) Search APIは1リクエストあたり最大200件なので、offsetをずらして全件取得します。 ...

GakuNin RDM APIをNode.jsで操作する — プロジェクト作成からGitHub+Vercel自動デプロイまで

GakuNin RDM APIをNode.jsで操作する — プロジェクト作成からGitHub+Vercel自動デプロイまで

はじめに GakuNin RDMは、国立情報学研究所(NII)が提供する研究データ管理プラットフォームです。Open Science Framework(OSF)をベースに構築されており、APIを通じてプロジェクトの操作を自動化できます。 本記事では、Node.jsからGakuNin RDM APIを使って以下の操作を行う方法を紹介します。 プロジェクトの作成・設定 Wikiの作成・更新 メンバーの追加 GitHub連携 + Vercelによる自動デプロイ 事前準備 パーソナルアクセストークンの取得 GakuNin RDMにログイン 設定 > パーソナルアクセストークンに移動 新しいトークンを作成(スコープ:osf.full_read、osf.full_write) プロジェクトの初期化 mkdir rdm && cd rdm npm init -y npm install dotenv .envファイルにトークンを保存します。 GRDM_TOKEN=your_personal_access_token_here .gitignoreも作成しておきます。 .env node_modules/ APIクライアントの作成 GakuNin RDMのAPIはJSON:API形式を採用しています。まず汎用的なクライアントをlib/client.jsとして作成します。 lib/client.js require("dotenv").config(); const BASE_URL = "https://api.rdm.nii.ac.jp/v2"; const TOKEN = process.env.GRDM_TOKEN; if (!TOKEN) { throw new Error("GRDM_TOKEN is not set in .env"); } const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/vnd.api+json", }; async function request(method, path, body) { const url = `${BASE_URL}${path}`; const options = { method, headers }; if (body) { options.body = JSON.stringify(body); } const res = await fetch(url, options); if (!res.ok) { const text = await res.text(); throw new Error(`${method} ${path} failed (${res.status}): ${text}`); } if (res.status === 204) return null; return res.json(); } module.exports = { request, headers, BASE_URL }; ポイント: ...

GakuNin RDM Search API (`/api/v1/search/`) 調査メモ

GakuNin RDM Search API (`/api/v1/search/`) 調査メモ

調査日 : 2026-02-24 対象 : GakuNin RDM (GRDM) の Search API ソースコード : RCOSDP/RDM-osf.io(website/search/ ディレクトリ) 開発者ガイド : RCOSDP/RDM-developer-guide 注意 : Search API の公式ドキュメントは確認できませんでした。本稿は API の実際の挙動とソースコードの両方に基づく調査記録です。 概要 GakuNin RDM は OSF (Open Science Framework) のフォークであり、ソースコードは GitHub (RCOSDP/RDM-osf.io) で公開されています。検索機能の実装は website/search/ ディレクトリにあり、主に以下のファイルで構成されています。 ファイル 役割 elastic_search.py インデックスのマッピング定義、ドキュメントの登録・更新 views.py API エンドポイントのハンドラ util.py build_private_search_query() 等のクエリ構築 search.py 上位インターフェース POST https://rdm.nii.ac.jp/api/v1/search/ Authorization: Bearer <パーソナルアクセストークン> 日本語環境では Elasticsearch の kuromoji_analyzer が使用されています(ソースコードで確認)。 リクエスト形式 { "api_version": {"vendor": "grdm", "version": 2}, "elasticsearch_dsl": { "query": { "filtered": { "query": { "query_string": { "default_field": "_all", "query": "検索キーワード" } } } }, "from": 0, "size": 10 }, "highlight": "title:30,name:30,user:30,text:124,comments.*:124", "sort": "modified_desc" } パラメータ 説明 備考 api_version vendor: "grdm", version: 2 version は 1 と 2 をサポートしています(ソースコードで確認) elasticsearch_dsl.query Elasticsearch Query DSL filtered 形式(ES 2.x 系構文)です from / size ページネーション size=100 まで動作を確認しています。match_all + size>50 では 500 エラーになります highlight フィールド名:文字数 形式 GRDM 独自フォーマットです。ワイルドカード(comments.*)も使えます sort ソート順 後述します sort の選択肢 ソースコード(util.py の build_private_search_query)によると、以下のソート対象が定義されています。 ...

AlfrescoをDockerで起動し、REST APIでレコード管理のライフサイクルを体験する

AlfrescoをDockerで起動し、REST APIでレコード管理のライフサイクルを体験する

概要 本記事では、Alfresco Governance Services Community Edition(以下AGS)の最新版(25.3.0)をDockerで起動し、REST APIを使ってレコード管理の一連のライフサイクルを体験します。 具体的には、以下の業務シナリオを想定します。 シナリオ: 契約書管理 業務部門が契約書を作成・登録する レコード管理者がレコードとして宣言し、ファイルプランに分類する 保持スケジュール(Retention Schedule)を設定する 契約終了後、カットオフ(現用→非現用)を実行する 保持期間(3年)の経過後、廃棄する 訴訟対応が発生した場合、ホールド(凍結)により廃棄を停止する 以下の前回の記事をベースに、最新版での構築手順とAPIの使い方を紹介します。 https://zenn.dev/nakamura196/articles/8da7161ff3df30 環境 acs-deployment: v10.2.0(2026年2月リリース) Alfresco Governance Repository Community: 25.3.0 Alfresco Governance Share Community: 25.3.0 Alfresco Search Services: 2.0.17 Traefik: 3.6 PostgreSQL: 16.5 セットアップ リポジトリのクローン git clone https://github.com/Alfresco/acs-deployment cd acs-deployment git checkout v10.2.0 cd docker-compose compose fileの作成 community-compose.yamlをベースに、Governance Services用のcompose fileを作成します。変更点は以下の3つです。 1. イメージの差し替え サービス 変更前 変更後 alfresco alfresco/alfresco-content-repository-community:25.3.0 alfresco/alfresco-governance-repository-community:25.3.0 share alfresco/alfresco-share:25.3.0 alfresco/alfresco-governance-share-community:25.3.0 2. 認証チケットのタイムアウト対策(後述) ...

KAKEN OpenSearch API の使い方

KAKEN OpenSearch API の使い方

科研費データベース(KAKEN)の情報をプログラムから取得する方法を解説します。 1. はじめに KAKENは、国立情報学研究所(NII)が提供する科学研究費助成事業データベースです。OpenSearch APIを利用することで、研究課題の情報をプログラムから取得できます。 2. 事前準備:アプリケーションIDの取得 KAKEN APIを利用するには、CiNiiでアプリケーションIDを取得する必要があります。 CiNii API利用登録にアクセス 必要事項を入力して登録申請 承認後、メールでApplication ID(appid)が届く 注意 : 登録から承認まで時間がかかる場合があります。 3. APIエンドポイント 研究課題をさがす https://kaken.nii.ac.jp/opensearch/ 研究者をさがす https://nrid.nii.ac.jp/opensearch/ 4. 主要パラメータ(研究課題をさがす) パラメータ 説明 必須 例 appid アプリケーションID ○ 82RKpPlZiIjbqKwFDO3D qb 研究課題番号で検索 △ 19K20626 kw フリーワード検索 △ IIIF qa 研究課題名で検索 △ デジタルアーカイブ qg 研究者の姓名で検索 △ 中村覚 qm 研究者番号で検索 △ 80802743 format レスポンス形式 - xml(デフォルト: html5) rw 1ページの件数 - 20, 50, 100, 200, 500 lang 言語 - ja, en △: いずれか1つ以上が必要 ...

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築

校異源氏物語テキストDBに対する検索を行うAPIサーバの構築

概要 校異源氏物語テキストDBに対する検索を行うAPIサーバの構築したので、備忘録です。 https://genji-api.aws.ldas.jp/ 背景 以下のページで、『校異源氏物語』のテキストデータをTEI/XMLに準拠した形で公開しています。 https://kouigenjimonogatari.github.io/ このテキストデータをElasticsearchに登録し、コマごとの検索を可能にするAPIを作成します。 使い方 以下のURLで、OpenAPIおよびSwaggerを用いた使い方の説明ページにアクセスできます。 https://genji-api.aws.ldas.jp/ 工夫点 検索語の展開 例えば以下のURLは、「夕顔」を検索キーワードとした例です。JSON:APIに準拠した入出力形式としています。 https://genji-api.aws.ldas.jp/search?q=夕顔&page[limit]=20&page[offset]=0&sort=page&filter[expandRepeatMarks]=true&filter[unifyKanjiKana]=true&filter[unifyHistoricalKana]=true&filter[unifyPhoneticChanges]=true&filter[unifyDakuon]=true&filter[vol_str]=04 夕顔 この時、以下のような結果が返却されます。入力したキーワード「夕顔」に対して、バリエーションを生成し、これらに基づく検索を行います。 { "data": [], "meta": { "query": "夕顔", "transformedQueries": [ "夕顔", "ゆうかお", "ゆふかお", "ゆふかほ", "ゆうかほ", "夕かお", "夕かほ", "ゆう顔", "ゆふ顔" ], "transformOptions": { "expandRepeatMarks": true, "unifyKanjiKana": true, "unifyHistoricalKana": true, "unifyPhoneticChanges": true, "unifyDakuon": true }, "filters": { "expandRepeatMarks": true, "unifyKanjiKana": true, "unifyHistoricalKana": true, "unifyPhoneticChanges": true, "unifyDakuon": true, "vol_str": "04 夕顔" }, "sort": "page", "limit": 20, "offset": 0, "total": 7, "aggregations": { "vol_str": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": "04 夕顔", "doc_count": 7 } ] } } } } その結果、本文中に登場する「ゆふかほ」「夕かほ」「夕顔」を一度に検索することができます。 この検索キーワードの展開については、検索オプションをON/OFFを切り替えられるようにしています。詳細は、上述したSwagger UIでご確認ください。 ...

DrupalのJSON:APIでcreatedやchangedに対するフィルタを適用する

DrupalのJSON:APIでcreatedやchangedに対するフィルタを適用する

概要 DrupalのJSON:APIでcreatedやchangedに対するフィルタを適用する方法の備忘録です。 背景 以下を参考にしました。 https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module/filtering 例えば、6/2以降に更新されたものだけをフィルタリングしようとした際、以下のクエリでは適切に動作しませんでした。 ?filter[a-label][condition][path]=changed&filter[a-label][condition][operator]=%3E%3D&filter[a-label][condition][value]=2025-06-02 正しい方法 以下の記事が参考になりました。 https://www.reddit.com/r/drupal/comments/1bdvu61/json_api_drupal_filter_on_date/ Note that timestamp fields (like created or changed) currently must use a timestamp for filtering: タイムスタンプフィールド(createdやchangedなど)は現在、フィルタリングにタイムスタンプを使用する必要があります。 例えば、2025/6/2のタイムスタンプ1748790000を用いて、以下のようなクエリを使用することで、正しくフィルタリングできました。 ?filter[a-label][condition][path]=changed&filter[a-label][condition][operator]=%3E%3D&filter[a-label][condition][value]=1748790000 まとめ DrupalのJSON:APIで、createdやchangedに対するフィルタを適用する際にお役に立てば幸いです。

DTS (Distributed Text Services)のビューア開発

DTS (Distributed Text Services)のビューア開発

概要 DTS (Distributed Text Services)のビューアを開発したので、備忘録です。 以下のURLからお試しいただけます。 https://dts-viewer.vercel.app/ja/ 背景 DTS (Distributed Text Services)の公式ページは以下です。 https://distributed-text-services.github.io/specifications/ 以下の記事でも取り上げました。 今回、このDTS仕様に一部準拠したビューアを開発しました。 使い方 以下がトップページです。フォームにDTSのURLを入力します。ページ下部で例を提供します。技術的には、Entry pointを使用しています。 コレクションの一覧ページです。Collection Endpointを使用しています。 以下のAPIを例としています。 リンクをたどると、以下のようなリソースの一覧ページに遷移します。 ダウンロードボタンを押すと、TEI/XMLが表示されます。Document Endpointを使用しています。 ナビゲーションボタンを押すと、アクセス可能な部分テキストの一覧が表示されます。Navigation Endpointを使用していますが、現時点で複数階層には非対応です。 リンクをクリックすると、以下のような部分テキストをダウンロードすることができます。 工夫点 公式ページに以下のように記載されています。 The DTS Specification is currently in a public comment period following the 1-alpha release (機械翻訳)DTS仕様は、1-alphaリリースの後、現在パブリックコメント期間中です。 このような背景のため、既存のDTSの記述方法にばらつきがありました。そこで内部でできるだけDTS API (1.0 Draft)に変換し、その結果を可視化するようにしています。 DTS仕様が成熟するにつれ、このような問題は解決されるかと思います。 まとめ DTS仕様は以下のように説明されています。 The Distributed Text Services (DTS) Specification defines an API for working with collections of text as machine-actionable data. ...

GakuNin RDMのAPIを用いて、連携したストレージのファイルを検索する

GakuNin RDMのAPIを用いて、連携したストレージのファイルを検索する

概要 以下の記事で、GakuNin RDMのAPIを用いたアプリケーション構築について紹介しました。 本記事では、GakuNin RDMのAPIを用いて、連携したストレージのファイルを検索する方法を紹介します。 実装例 次のような形で、検索APIを実装しました。なお、https://rdm.nii.ac.jp/api/v1/search/file/にクライアントから直接アクセスした際には、CORSによるエラーが発生したため、Next.jsのAPI Routesとして実装しています。 import { NextResponse } from "next/server"; import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; import { getServerSession } from "next-auth"; export async function GET(req: Request) { const session = await getServerSession(authOptions); // URLからクエリパラメータを取得 const url = new URL(req.url); const query = url.searchParams.get("filter[fulltext]") || ""; const offset = parseInt(url.searchParams.get("page[offset]") || "0", 10); const size = parseInt(url.searchParams.get("page[limit]") || "20", 10); const accessToken = session?.accessToken; const apiUrl = "https://rdm.nii.ac.jp/api/v1/search/file/"; const params = { api_version: { vendor: "grdm", version: 2 }, sort: "created_desc", highlight: "title:30,name:30,user:30,text:124,comments.*:124", elasticsearch_dsl: { query: { filtered: { query: { query_string: { default_field: "_all", fields: [ "_all", "title^4", "description^1.2", "job^1", "school^1", "all_jobs^0.125", "all_schools^0.125", ], query, analyze_wildcard: true, lenient: true, }, }, }, }, from: offset, size, }, }; const res = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, }, body: JSON.stringify(params), }); const data = await res.json(); return NextResponse.json(data); } 利用例 以下のURLからお試しいただけます。(利用にあたっては、GakuNin RDMへのログインが必要です。) ...

GakuNin RDM(OSF)のAPIで、フィルタを使う

GakuNin RDM(OSF)のAPIで、フィルタを使う

概要 GakuNin RDM(OSF)のAPIで、フィルタを使う方法の備忘録です。 対象データ 以下のようなファイル構造を持つ「NII Storage」を対象にします。 APIでは、以下のようなURLでアクセスできるものを対象にします。 https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/files/osfstorage/ JSONデータの例は以下です。 { "data": [ { "id": "67ce5b0b2fe4740010f753c0", "type": "files", "attributes": { "guid": "ungd3", "checkout": null, "name": "IMG_8269.png", "kind": "file", "path": "/67ce5b0b2fe4740010f753c0", "size": 952107, "provider": "osfstorage", "materialized_path": "/IMG_8269.png", "last_touched": null, "date_modified": "2025-03-10T03:22:51.750550Z", "date_created": "2025-03-10T03:22:51.750550Z", "extra": { "hashes": { "md5": "e57192b30103a7e995597ceaea39cbbf", "sha256": "5e282187067a53aaab0f1f00daaefb9519d60b064831403e671662cfbcf6f41f" }, "downloads": 0 }, "tags": [], "current_user_can_comment": true, "current_version": 1 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "versions": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/versions/?format=json", "meta": {} } } }, "comments": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/comments/?format=json&filter%5Btarget%5D=ungd3", "meta": {} } } }, "metadata_records": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/metadata_records/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67ce5b0b2fe4740010f753c0", "download": "https://rdm.nii.ac.jp/download/ungd3/", "render": "https://mfr.rdm.nii.ac.jp/render?url=https://rdm.nii.ac.jp/download/ungd3/?direct%26mode=render", "html": "https://rdm.nii.ac.jp/wzv9g/files/osfstorage/67ce5b0b2fe4740010f753c0", "self": "https://api.rdm.nii.ac.jp/v2/files/67ce5b0b2fe4740010f753c0/" } }, { "id": "67da847416000900109e0454", "type": "files", "attributes": { "guid": "b45mp", "checkout": null, "name": "01.xml", "kind": "file", "path": "/67da847416000900109e0454", "size": 79397, "provider": "osfstorage", "materialized_path": "/01.xml", "last_touched": null, "date_modified": "2025-03-19T13:24:27.868078Z", "date_created": "2025-03-19T08:46:44.636107Z", "extra": { "hashes": { "md5": "a3824b2f49471842d1046a2abe623284", "sha256": "83d18a6e52a52597ebac6fa1eb8a137ed6e1e64b9c0e2c1a0b49cf746a777d0a" }, "downloads": 0 }, "tags": [], "current_user_can_comment": true, "current_version": 5 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "versions": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/versions/?format=json", "meta": {} } } }, "comments": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/comments/?format=json&filter%5Btarget%5D=b45mp", "meta": {} } } }, "metadata_records": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/metadata_records/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67da847416000900109e0454", "download": "https://rdm.nii.ac.jp/download/b45mp/", "render": "https://mfr.rdm.nii.ac.jp/render?url=https://rdm.nii.ac.jp/download/b45mp/?direct%26mode=render", "html": "https://rdm.nii.ac.jp/wzv9g/files/osfstorage/67da847416000900109e0454", "self": "https://api.rdm.nii.ac.jp/v2/files/67da847416000900109e0454/" } }, { "id": "67daca9916000900109e1d98", "type": "files", "attributes": { "guid": null, "checkout": null, "name": "test", "kind": "folder", "path": "/67daca9916000900109e1d98/", "size": null, "provider": "osfstorage", "materialized_path": "/test/", "last_touched": null, "date_modified": null, "date_created": null, "extra": { "hashes": { "md5": null, "sha256": null } }, "tags": [], "current_user_can_comment": true, "current_version": 1 }, "relationships": { "parent_folder": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/files/674034a483bdc200108b8a95/?format=json", "meta": {} } }, "data": { "id": "674034a483bdc200108b8a95", "type": "files" } }, "files": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/files/osfstorage/67daca9916000900109e1d98/?format=json", "meta": {} } } }, "node": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/?format=json", "meta": {} } }, "data": { "id": "wzv9g", "type": "nodes" } }, "target": { "links": { "related": { "href": "https://api.rdm.nii.ac.jp/v2/nodes/wzv9g/", "meta": { "type": "node" } } }, "data": { "type": "node", "id": "wzv9g" } } }, "links": { "info": "https://api.rdm.nii.ac.jp/v2/files/67daca9916000900109e1d98/", "move": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "upload": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "delete": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/", "new_folder": "https://files.rdm.nii.ac.jp/v1/resources/wzv9g/providers/osfstorage/67daca9916000900109e1d98/?kind=folder", "self": "https://api.rdm.nii.ac.jp/v2/files/67daca9916000900109e1d98/" } } ], "links": { "first": null, "last": null, "prev": null, "next": null, "meta": { "total": 3, "per_page": 10 } } } 検索例 JSON:APIに準拠しているので、filterパラメータを使用します。 ...

AtoM(Access to Memory)のAPIを使って、オブジェクトを登録してみる

AtoM(Access to Memory)のAPIを使って、オブジェクトを登録してみる

概要 AtoM(Access to Memory)のAPIを使って、オブジェクトを登録する方法の備忘録です。 APIの有効化 以下にアクセスします。 /sfPluginAdminPlugin/plugins arRestApiPluginを有効にします。 APIキーの取得 以下に、APIキーを生成する方法が説明されています。 https://www.accesstomemory.org/en/docs/2.9/dev-manual/api/api-intro/#generating-an-api-key-for-a-user ユーザ名とパスワードでもAPI接続できるようですが、今回はREST API Keyを発行しました。 エンドポイント AtoMでは、「典拠レコード」や「機能」など、複数のメニューが提供されていますが、APIによって利用できるのは、以下のみのようです。 See the subsequent pages for more details on each endpoint, and available parameters. There are three endpoints available: Browse taxonomy terms Browse information objects Read information object Download digital objects Add physical objects この点は、ArchivesSpaceのほうが豊富なAPIが提供されており、軍配が上がるかもしれません。 https://archivesspace.github.io/archivesspace/api/ また、以下のソースコードを確認すると、CreateActionが可能なものは、informationobjectsとphysicalobjects、digitalobjectsに限定されているようでした。 https://github.com/artefactual/atom/tree/qa/2.x/plugins/arRestApiPlugin/modules/api/actions ただ機械的に一括登録を行いたい場面は、主にinformationobjectsだと考えられるため、これらの機能のみで十分かもしれません。 physical objectsの登録 以下のようなクラスを用意します。 #| export class ApiClient: def __init__(self): load_dotenv(override=True) self.url = os.getenv("atom_url") username = os.getenv("username") password = os.getenv("password") api_key = os.getenv("api_key") if api_key: self.headers = { "REST-API-Key": api_key, "Content-Type": "application/json" } else: # Basic 認証のヘッダーを作成 auth_string = f"{username}:{password}" auth_bytes = auth_string.encode('ascii') auth_b64 = base64.b64encode(auth_bytes).decode('ascii') self.headers = { "Authorization": f"Basic {auth_b64}", "Content-Type": "application/json" } def add_physical_objects(self, physical_objects): url = f"{self.url}/api/physicalobjects" print(url, self.headers, physical_objects) response = requests.post(url, headers=self.headers, json=physical_objects) # レスポンスを確認 if response.status_code in [200, 201]: print("物理オブジェクトが作成されました!") print(f"ステータスコード: {response.status_code}") print(f"レスポンス: {response.text}") # 作成されたオブジェクトの情報 result = response.json() print(f"作成された物理オブジェクトID: {result.get('id')}") print(json.dumps(result, indent=4)) else: print(f"エラー: {response.status_code}") print(f"レスポンス: {response.text}") 以下で実行します。 ...

GakuNin RDMのAPIを使って、ファイルのアップロードなどを行う

GakuNin RDMのAPIを使って、ファイルのアップロードなどを行う

背景 GakuNin RDMのAPIを使って、ファイルのアップロードなどを行う方法の備忘録です。 参考 以下でPAT(パーソナルアクセストークン)の取得方法などを説明しています。 また以下では、OAuth (Open Authorization)を使った方法を紹介しています。Webアプリなどから使用される場合には、こちらが参考になりましたら幸いです。 方法 nbdevを使って、以下のリポジトリを作成しました。 https://github.com/nakamura196/grdm-tools 以下でドキュメントを確認できます。 https://nakamura196.github.io/grdm-tools/ プロバイダ(osfstorage)とフォルダのID(6735a92e6dc8e1001062ac08)は変更する必要がありますが、以下のようなスクリプトにより、特定のフォルダにファイルをアップロードできます。 from grdm_tools.api import GrdmClient import os client = GrdmClient( token=os.environ.get('GRDM_TOKEN') ) project_id = "ys86g" file_path = "./sample.png" url = f"https://files.rdm.nii.ac.jp/v1/resources/{project_id}/providers/osfstorage/6735a92e6dc8e1001062ac08/?kind=file" client.upload_file(file_path, url) ソースコードは以下からご確認いただけます。 https://nakamura196.github.io/grdm-tools/api.html#grdmclient.upload_file まとめ GakuNin RDMのAPI利用にあたり、参考になりましたら幸いです。