のいのログ

SharePoint REST APIでリストのアイテムのバージョンを復元する方法

SharePoint REST APIでSharePointリストのレコードのバージョンを復元する方法が、公式ドキュメントの記述と実際の方法に違いがありすぎてややこしかったのでメモ。

目次(クリックでジャンプできます)

SharePoint REST API v2を利用するのが正解

Microsoft Graph APIの機能を利用できるSharePoint REST API v2にあるエンドポイントを利用します。

アイテムのバージョンの一覧を取得する

GET _api/v2.1/sites('{site-id}')/lists/{list-id}/items/{item-id}/versions

取得すると以下のような結果を得られます。

{
        "@odata.context": "https://example.sharepoint.com/sites/{site-name}/_api/v2.1/$metadata#sites('{site-id}')/lists('{list-id}')/items('{item-id}')/versions",
        "value": [
            {
                "id": "2.0",
                "lastModifiedDateTime": "2025-11-08T00:15:20Z",
                "lastModifiedBy": {
                    "user": {
                        "displayName": "なつぐれ",
                        "email": "aaa@example.com"
                    }
                },
                "published": {
                    "level": "published",
                    "versionId": "2.0"
                }
            },
            {
                "id": "1.0",
                "lastModifiedDateTime": "2025-11-08T00:14:16Z",
                "lastModifiedBy": {
                    "user": {
                        "displayName": "なつぐれ",
                        "email": "aaa@exmaple.com"
                    }
                },
                "published": {
                    "level": "published",
                    "versionId": "1.0"
                }
            }
        ]
    }
}

$expand=fieldクエリをつけると、各バージョン時のフィールドの値も取得できます。

$expand=field時の出力
{
        "@odata.context": "https://example.sharepoint.com/sites/{site-name}/_api/v2.1/$metadata#sites('{site-id}')/lists('{list-id}')/items('{item-id}')/versions(fields())",
        "value": [
            {
                "id": "2.0",
                "lastModifiedDateTime": "2025-11-08T00:15:20Z",
                "lastModifiedBy": {
                    "user": {
                        "displayName": "なつぐれ",
                        "email": "aaa@example.com"
                    }
                },
                "published": {
                    "level": "published",
                    "versionId": "2.0"
                },
                "fields": {
                    "ID": "1",
                    "Title": "青りんご🍏",
                    "Modified@odata.type": "#DateTimeOffset",
                    "Modified": "2025-11-08T00:15:20-08:00",
                    "Created@odata.type": "#DateTimeOffset",
                    "Created": "2025-11-08T00:14:16-08:00",
                    "Author": "6;#なつぐれ,#i:0#.f|membership|aaa@example.com,#aaa@example.com,#aaa@example.com,#なつぐれ",
                    "Editor": "6;#なつぐれ,#i:0#.f|membership|aaa@example.com,#aaa@example.com,#aaa@example.com,#なつぐれ",
                    "_UIVersionString": "2.0",
                    "Attachments": false,
                    "Edit": "",
                    "ItemChildCount": "0",
                    "FolderChildCount": "0",
                    "_ComplianceFlags": "",
                    "_ComplianceTag": "",
                    "_ComplianceTagWrittenTime": "",
                    "_ComplianceTagUserId": ""
                }
            },
            {
                "id": "1.0",
                "lastModifiedDateTime": "2025-11-08T00:14:16Z",
                "lastModifiedBy": {
                    "user": {
                        "displayName": "なつぐれ",
                        "email": "aaa@example.com"
                    }
                },
                "published": {
                    "level": "published",
                    "versionId": "1.0"
                },
                "fields": {
                    "ID": "1",
                    "Title": "りんご",
                    "Modified@odata.type": "#DateTimeOffset",
                    "Modified": "2025-11-08T00:14:16-08:00",
                    "Created@odata.type": "#DateTimeOffset",
                    "Created": "2025-11-08T00:14:16-08:00",
                    "Author": "6;#なつぐれ,#i:0#.f|membership|aaa@example.com,#aaa@example.com,#aaa@example.com,#なつぐれ",
                    "Editor": "6;#なつぐれ,#i:0#.f|membership|aaa@example.com,#aaa@example.com,#aaa@example.com,#なつぐれ",
                    "_UIVersionString": "1.0",
                    "Attachments": false,
                    "Edit": "",
                    "ItemChildCount": "0",
                    "FolderChildCount": "0",
                    "_ComplianceFlags": "",
                    "_ComplianceTag": "",
                    "_ComplianceTagWrittenTime": "",
                    "_ComplianceTagUserId": ""
                }
            }
        ]
    }

指定したバージョンに復元する

POST _api/v2.0/sites('{site-id}')/lists/{list-id}/items/{item-id}/versions/{version-id}/restoreVersion

バージョンを復元するには上記のエンドポイントにPOSTします。

APIのバージョンが先程と違ってv2.0になっていることに注意してください。

取得はv2.1、復元はv2.0にしないとエラーになってしまいます。

復元後のアイテムを見ると、v2.0時に上書きされた「青りんご🍏」がv1.0の「りんご」に戻り、バージョン番号は3.0に書き換わっていることが確認できます。

※SharePointのアイテムのバージョン番号は、復元した際もそのバージョン番号に戻らず、次のバージョン番号にインクリメントされる仕様です。

実践的な使い方

リレーション関係にあるリストの一括更新で、1つでも更新が失敗した際にすべての更新をロールバックする

SharePointリストには多くのリレーショナルデータベース管理システム(RDBMS)に搭載されているトランザクション処理がありません。
※Dataverseでは変更セットと呼ばれているものです。

参照列などで親子関係になっているリストを同時に更新するとします。

その際、いずれかのリストの更新に失敗することもありえます。他の成功したリストの更新をそのまま適用してしまうと、データの整合性が取れなくなってしまいます。

それを防ぐため、すべての処理が成功したときだけ更新を適用(COMMIT)し、失敗した場合は変更をすべて元に戻す(ROLLBACK)処理を実装できるのがトランザクションです。

このトランザクションがSharePointリストにはないため、エラー時の不整合を防ぐためにはエラーを検知してロールバックする処理を自分で実装する必要があります。

アプリから呼び出せるPower Automate フローを作る

「Power Apps がフローを呼び出したとき (V2)」トリガーでitemIdとversionIdを受け取り、これを利用して復元用のエンドポイントにリクエストします。

最後のstatusCodeによる分岐は本来必要ないですが、個人的に「成功しても失敗してもアプリへ応答する+フローの実行履歴に成功と失敗を記録する」の両方を実装したかったためにこうなっています。厳密に記録する必要がなければ、statusCodeの分岐は省いても問題ないと思います。

Power Apps側の実装

たとえば、Power Appsにユーザーが入力した内容を複数のテーブルにまたがって更新する際、IfErrorでエラーをキャッチし、2つ目以降のリストの更新に失敗した場合は、前セクションで作成したフローにアイテムのIDとバージョン番号を引数として渡してロールバックさせます。

// 更新前のバージョン番号が必要なので、アプリの実装によってはここで更新前のレコードを変数に保存しておく。
UpdateContext({ _oldValue: _currentItem });

IfError(
    // try 1
    Patch(親リスト, _currentItem, { タイトル = "伊予柑" })
    // catch 1
    Notify("親レコードの登録に失敗しました。", NotificationType.Error),

    // try 2
    Error("Something went wrong."), // ここで子レコードの更新に失敗したとする。
    // catch 2
    親レコードのバージョンを復元.Run(_oldValue.ID, _oldValue.バージョン番号);
    Notify("レコードの更新に失敗したため、変更前の状態に復元しました。", NotificationType.Error),

    // defaultResult
    Notify("更新が正常に完了しました。", NotificationType.Success)
)

上記のコードを実行すると、更新されたリストのアイテムは即座にロールバックされています。

「伊予柑」に更新されたものが、前バージョンの「みかん」に即座にロールバックされている

注意点

公式ドキュメントは存在しているが、なぜか目次から省かれている

ListItemVersionリソースタイプに関するドキュメントは、なぜかGraph API Referenceの目次から省かれています。

公式ドキュメントを確認したい方は以下のリンクから飛んでください。

documentSetVersionはリストのアイテムに対しては使用できない

API ReferenceのList Itemを辿っていくとそこにもrestoreという項目があります。

しかし、これはdocumentSetVersionに対するもので、リストアイテム単体のバージョンを復元するためには利用できません。

SharePointのドキュメントセットという機能を私も知らなかったのですが、どうやら複数のファイルをまとめたプリセットやコンテンツタイプを作成できる機能みたいです。

興味がある方はMicrosoft MVPのコルネさんの解説資料を読んでみてください。

REST API v1のエンドポイントは利用不可?

SharePoint REST API v1にもリストアイテムを復元するエンドポイントがありますが、なぜか利用できませんでした。

バージョンの一覧は取得できたのですが、restoreVersionのエンドポイントにリクエストすると、「リソースrestoreVersionが見つかりません」というエラーが返ってきてしまいます。

英語版の記事の最終更新が2021年なので、Graph APIが代わる形でひっそりと削除されてしまったのかもしれません。

おわりに

ついつい更新リクエストを投げっぱなしで失敗したときの実装を忘れてしまいますが、プログラムにエラーはつきものですので、本番運用中のもしもの事態に備えておきたいですね。

それにしても相変わらずMicrosoft Learnはわかりづらいですね。せめて、不正確な情報を載せるのはやめていただきたい…。

シェアしていただけると嬉しいです!
  • URLをコピーしました!

この記事を書いた人

ローコード・RPAエンジニア。DX・業務効率化を専門に開発。

前職では鉄道運転士として働きながら、社内複業でSwift・Power Platformで業務効率化を推進していた。

応援する

コメント

コメントする

CAPTCHA


目次(クリックでジャンプできます)