概要
REST API(Representational State Transfer API)は、2000年にRoy Fieldingがその博士論文で提唱したアーキテクチャスタイル「REST」の原則に基づいて設計されたWebインターフェイスです。REST自体はプロトコルではなく設計思想であり、HTTP上でリソース指向のAPIを構築するための一連の制約(制約条件群)として定義されています。
IoT分野において、REST APIはクラウドプラットフォームとデバイスを繋ぐ主要インターフェイスとして広く使われています。AWS IoT、Azure IoT Hub、Google Cloud IoT、Ambientなど、ほぼすべてのIoTクラウドサービスがREST APIを提供しており、センサーデータのアップロード、デバイス設定の変更、ファームウェア更新のトリガーなど多様な操作が行えます。
REST APIの最大の強みは標準的なHTTPを使うため、あらゆるプログラミング言語・プラットフォームから呼び出せる汎用性にあります。ESP32からPython、JavaScriptまで、同じAPIエンドポイントを共通インターフェイスとして使えます。
歴史・背景
1990年代後半、SOAP(Simple Object Access Protocol)やCORBA、XML-RPCなど複雑なWebサービス規格が乱立していました。これらはWSDLによる複雑なインターフェイス定義を必要とし、実装コストが高いという課題がありました。
2000年、カリフォルニア大学アーバイン校のRoy Fieldingは博士論文「Architectural Styles and the Design of Network-based Software Architectures」の中でRESTを提唱。HTTPそのものの設計に内在するアーキテクチャパターンを体系化したものです。
2000年代後半からREST APIが急速に普及し、TwitterやFacebook、Google MapsのAPIが「RESTful」を標榜するようになると、Webサービスのデファクトスタンダードになりました。IoTの普及期(2010年代)にはほぼすべてのクラウドIoTプラットフォームがREST APIをメインインターフェイスとして採用しました。
技術仕様
RESTの6原則
RESTアーキテクチャは以下の6つの制約から成ります。
| 制約 | 説明 |
|---|---|
| クライアント-サーバー分離 | UIとデータ管理を分離し、各々独立して進化できる |
| ステートレス | 各リクエストは完結している(サーバーはセッション状態を保持しない) |
| キャッシュ可能 | レスポンスにキャッシュ可否を明示できる |
| 統一インターフェイス | リソースはURIで識別し、HTTPメソッドで操作 |
| 階層化システム | プロキシ・ゲートウェイを介した多層構成を許容 |
| コードオンデマンド(任意) | 実行可能コードをダウンロードして動的に動作拡張(任意) |
HTTPメソッドとCRUD対応
GET /devices → 全デバイス一覧取得(Read)
GET /devices/{id} → 特定デバイス取得(Read)
POST /devices → 新規デバイス登録(Create)
PUT /devices/{id} → デバイス情報全更新(Update)
PATCH /devices/{id} → デバイス情報部分更新(Update)
DELETE /devices/{id} → デバイス削除(Delete)
リソース設計例(IoTデバイス管理API)
# デバイス管理
GET /api/v1/devices
POST /api/v1/devices
GET /api/v1/devices/{device_id}
PATCH /api/v1/devices/{device_id}
DELETE /api/v1/devices/{device_id}
# センサーデータ
GET /api/v1/devices/{device_id}/measurements
POST /api/v1/devices/{device_id}/measurements
GET /api/v1/devices/{device_id}/measurements/{id}
# ファームウェア
GET /api/v1/firmware/latest
POST /api/v1/devices/{device_id}/ota/trigger
# コマンド送信
POST /api/v1/devices/{device_id}/commands
GET /api/v1/devices/{device_id}/commands/{cmd_id}
認証方式
| 方式 | 説明 | 用途 |
|---|---|---|
| API Key | HTTPヘッダーに固定キーを付与 | シンプルなデバイス認証 |
| Bearer Token | JWTトークン(OAuth 2.0) | ユーザー認証・権限管理 |
| Basic Auth | Base64(user:pass) | テスト用途のみ推奨 |
| mTLS | クライアント証明書認証 | 高セキュリティデバイス認証 |
| HMAC署名 | リクエストへの署名(AWS SigV4等) | AWSデバイス認証 |
OpenAPIによるAPI定義
openapi: 3.0.0
info:
title: IoT Sensor API
version: 1.0.0
paths:
/devices/{device_id}/measurements:
post:
summary: センサーデータを送信
parameters:
- name: device_id
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
temperature:
type: number
humidity:
type: number
timestamp:
type: string
format: date-time
responses:
'201':
description: 登録成功
content:
application/json:
schema:
$ref: '#/components/schemas/Measurement'
動作原理
ステートレス通信
RESTの核心はステートレスです。各リクエストはそれ単体で完結しており、サーバーはリクエスト間の状態を覚えません。
# 良い例:各リクエストに認証情報を含む
POST /api/v1/measurements HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Content-Type: application/json
{"temperature": 25.3, "device_id": "sensor_01"}
# 悪い例:「前回のログインセッションを使って」という暗黙の依存
POST /api/v1/measurements HTTP/1.1
Session-ID: abc123 ← セッション状態への依存(RESTではない)
HATEOASによるリンクナビゲーション
成熟したREST設計ではレスポンスに次のアクションへのリンクを含めます(HATEOAS)。
{
"device_id": "sensor_01",
"status": "active",
"_links": {
"self": {"href": "/api/v1/devices/sensor_01"},
"measurements": {"href": "/api/v1/devices/sensor_01/measurements"},
"commands": {"href": "/api/v1/devices/sensor_01/commands"},
"firmware_update": {"href": "/api/v1/devices/sensor_01/ota/trigger", "method": "POST"}
}
}
バージョニング
APIの後方互換性を維持しながら進化させるためのバージョニング戦略です。
# URLパスバージョニング(最も一般的)
/api/v1/devices
/api/v2/devices
# ヘッダーバージョニング
Accept: application/vnd.example.v2+json
# クエリパラメータバージョニング
/api/devices?version=2
用途・ユースケース
センサーデータのクラウド送信
# Raspberry Pi から REST API でデータ送信
import requests
import json
from datetime import datetime
import Adafruit_DHT
API_BASE = "https://api.iotplatform.example.com/v1"
API_KEY = "your-api-key"
DEVICE_ID = "rpi-greenhouse-01"
def send_sensor_data():
sensor = Adafruit_DHT.DHT22
humidity, temperature = Adafruit_DHT.read_retry(sensor, 4)
payload = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"temperature": round(temperature, 1),
"humidity": round(humidity, 1)
}
response = requests.post(
f"{API_BASE}/devices/{DEVICE_ID}/measurements",
json=payload,
headers={"X-API-Key": API_KEY},
timeout=10
)
response.raise_for_status()
return response.json()
デバイス設定の読み書き
# デバイス設定を取得
config = requests.get(
f"{API_BASE}/devices/{DEVICE_ID}/config",
headers={"X-API-Key": API_KEY}
).json()
# 送信間隔を変更(部分更新)
requests.patch(
f"{API_BASE}/devices/{DEVICE_ID}/config",
json={"report_interval_sec": 300},
headers={"X-API-Key": API_KEY}
)
OTAトリガー
# 新しいファームウェアバージョンを確認
latest = requests.get(f"{API_BASE}/firmware/latest").json()
current_version = "1.2.0"
if latest["version"] != current_version:
# OTAアップデートをトリガー
requests.post(
f"{API_BASE}/devices/{DEVICE_ID}/ota/trigger",
json={"firmware_version": latest["version"]},
headers={"X-API-Key": API_KEY}
)
コマンド送信と結果ポーリング
# コマンド送信(非同期)
resp = requests.post(
f"{API_BASE}/devices/{DEVICE_ID}/commands",
json={"type": "reboot", "params": {}},
headers={"X-API-Key": API_KEY}
)
cmd_id = resp.json()["command_id"]
# 結果をポーリング
import time
for _ in range(30):
result = requests.get(
f"{API_BASE}/devices/{DEVICE_ID}/commands/{cmd_id}",
headers={"X-API-Key": API_KEY}
).json()
if result["status"] in ["completed", "failed"]:
break
time.sleep(2)
実装・開発のポイント
エラーハンドリングのベストプラクティス
RFC 7807(Problem Details for HTTP APIs)に準拠したエラーレスポンス設計が推奨されます。
{
"type": "https://api.example.com/errors/device-offline",
"title": "デバイスがオフラインです",
"status": 503,
"detail": "device_id 'sensor_01' は最後の接続から30分以上経過しています",
"instance": "/devices/sensor_01"
}
レート制限の扱い
IoTデバイスが高頻度でAPIを叩く場合、429エラーとRetry-Afterヘッダーを適切に処理します。
// C言語での再試行処理(ESP-IDF)
int http_post_with_rate_limit(const char *url, const char *body) {
int retry_after = 1;
for (int attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
int status = http_post(url, body);
if (status == 200 || status == 201) return 0;
if (status == 429) {
// Retry-Afterヘッダーの値を取得
retry_after = get_retry_after_header();
ESP_LOGW(TAG, "Rate limited. Retry after %d sec", retry_after);
vTaskDelay(pdMS_TO_TICKS(retry_after * 1000));
} else if (status >= 500) {
// サーバーエラーは指数バックオフ
vTaskDelay(pdMS_TO_TICKS(1000 * (1 << attempt)));
} else {
return -1; // 4xxは再試行しない
}
}
return -1;
}
冪等性の設計
PUT・DELETE・GETは冪等(同じリクエストを何度実行しても結果が変わらない)に設計します。ネットワーク障害時の再送安全性に直結します。
# 冪等なPUT(全フィールドを毎回送る)
PUT /devices/sensor_01
{"name": "温室センサー", "location": "A棟", "report_interval": 60}
# 冪等でないPOST(毎回新しいレコードが作られる可能性)
POST /devices/sensor_01/measurements
{"temperature": 25.3}
→ 重複送信防止のためidempotency-keyヘッダーを使う
他技術との比較
| 項目 | REST API | GraphQL | gRPC | MQTT |
|---|---|---|---|---|
| プロトコル | HTTP | HTTP | HTTP/2 | TCP |
| スキーマ定義 | OpenAPI(任意) | 必須 | Protocol Buffers | なし |
| ペイロード | JSON/XML等 | JSON | バイナリ | 任意 |
| ストリーミング | △(Webhook) | ○(Subscription) | ◎ | ◎ |
| 学習コスト | 低 | 中 | 中 | 低 |
| 組み込み適合 | ○ | △ | △ | ◎ |
MQTTやCoAPと比較してREST APIはより重量級ですが、クラウドサービスとの統合やデバッグのしやすさで優れています。JSONと組み合わせることで可読性の高いAPIを構築でき、WebSocketと組み合わせることでリアルタイム通知も実現できます。