REST API
概要
Arrival Space REST API により、サードパーティアプリケーションは、ユーザー認証、署名付きURLによるアセットのアップロード、非同期処理ジョブの追跡、スペースの作成と管理、エンティティの管理、ゲートの設定が可能です。
機能
- 署名付き S3 URL によるアセットのアップロードと処理の確認
- リアルタイムの進捗情報を含む非同期処理ジョブの追跡
- アップロードからのスペース作成とプライバシー設定の更新
- スペース内のエンティティの作成、読み取り、更新、削除
- エンティティエンドポイントを使用した静的および動的ゲートの設定
アップロードを開始する前に、十分な利用可能ストレージが残っていることを確認してください。そうしないと、アップロードが失敗する場合があります。
ベースURL & バージョニング
const API_BASE_URL = "https://api-staging.arrival.space/api";
const VERSION = "v1";
すべてのエンドポイントには ${API_BASE_URL}/${VERSION} のプレフィックスを付ける必要があります。
APIエンドポイントは将来変更される可能性があります。接続の問題が発生した場合は、サポートチームまでお問い合わせください。
認証
すべてのAPIリクエストには、Authorization ヘッダーに Bearer トークンが必要です:
Authorization: Bearer <api_key>
オプション1:ユーザーアカウントAPIキー
- Arrival Space にログインします
- アカウント設定に移動します
- APIキーを生成またはコピーします
オプション2:OAuth 2.0 ログイン(ネイティブアプリ向け)
ネイティブアプリケーションは OAuth 2.0 Authorization Code flow with PKCE を使用して、ユーザーが Arrival.Space アカウント(Google サインインまたはメール/パスワード)でログインできるようにします。これは、ユーザーが手動で API キーをコピーする必要がないサードパーティ統合に推奨されるアプローチです。
OAuth フローは標準的なエンドポイントを使用し、アクセストークンとして API キーを返します。これはユーザーのアカウント設定で表示されるものと同じキーです。つまり、トークンはサーバーの再起動後も有効で、すべてのAPIエンドポイントで同一に動作します。
OAuth エンドポイント:
| エンドポイント | 説明 |
|---|---|
GET /.well-known/oauth-authorization-server | OAuth 2.0 サーバーメタデータ(自動検出) |
POST /register | 動的クライアント登録(RFC 7591) |
GET /authorize | 認可エンドポイント — ログインページにリダイレクト |
POST /token | トークン交換 — アクセストークンを返却 |
POST /revoke | トークン失効 |
フローの概要:
-
クライアントを登録(一度だけ、または初回起動時):
const regResponse = await fetch(`${API_BASE_URL}/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_name: "My App",
redirect_uris: ["https://myapp.example.com/callback"],
grant_types: ["authorization_code"],
response_types: ["code"],
token_endpoint_auth_method: "none"
})
});
const client = await regResponse.json();
// Save client.client_id for future use -
PKCEチャレンジを生成し、ユーザーを認可ページにリダイレクト:
// Generate PKCE code verifier + challenge
const codeVerifier = generateRandomString(64);
const codeChallenge = base64url(sha256(codeVerifier));
// Open browser to:
const authUrl = new URL(`${API_BASE_URL}/authorize`);
authUrl.searchParams.set("client_id", client.client_id);
authUrl.searchParams.set("redirect_uri", "https://myapp.example.com/callback");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("state", generateRandomString(32));ユーザーには、Google サインインとメール/パスワードオプションを含む Arrival.Space ログインページが表示されます。
-
コールバックを処理 — ログイン後、ブラウザは
?code=...&state=...付きでredirect_uriにリダイレクトされます:// Your callback handler receives: code, state
const tokenResponse = await fetch(`${API_BASE_URL}/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: code,
redirect_uri: "https://myapp.example.com/callback",
client_id: client.client_id,
code_verifier: codeVerifier
})
});
const tokens = await tokenResponse.json();
const apiKey = tokens.access_token; -
トークンを使用 — 標準の API キーと同じように使用できます:
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces`, {
headers: { Authorization: `Bearer ${apiKey}` }
});
- OAuth フローで返される
access_tokenは、ユーザーの実際の API キーです — アカウント設定に表示されるものと同じです - トークンは期限切れになりません(
expires_inの制限なし) — ユーザーがキーを取り消すまで有効です - アクセストークンが期限切れにならないため、リフレッシュトークンは不要です
- ユーザーが既に API キーを持っている場合、既存のキーが返されます(重複は作成されません)
サーバーがリバースプロキシ(Apache、nginx)の背後にある場合、/.well-known/oauth-authorization-server が Node.js バックエンドに転送されるようにしてください。ディスカバリーが利用できない場合、クライアントはエンドポイントを直接使用できます:/authorize、/token、/register。
OAuth エンドポイントは Model Context Protocol (MCP) とも互換性があります。Claude Desktop などの MCP クライアントは https://api-dev.arrival.space/api/v1/mcp を使用して直接接続できます — OAuth フローは MCP クライアントによって自動的に処理されます。
規約
- レスポンスは
status、data、場合によってはmessageまたはerrorを含む共通のエンベロープを使用します - タイムスタンプは ISO 8601 文字列です(例:
2025-01-15T10:30:00.000Z) - 一部のオペレーションは非同期で、
job_idを返します。job_statusがcompletedまたはfailedになるまでGET /jobs/:jobIdでポーリングしてください resource_keyはアップロードされたアセットを識別し、スペースやエンティティの作成に使用されますspaceIdはスペースURLに含まれる一意の識別子です(例:https://arrival.space/12345678_9012)entity_idはスペース内の特定のエンティティを識別します
レート制限
制限は API キーごとに適用されます(キーが提供されない場合はIPにフォールバック)。429 レスポンスには Retry-After ヘッダーとボディの retry_after_seconds フィールドが含まれます。
| スコープ | 制限 |
|---|---|
| すべてのAPIエンドポイント | 300リクエスト/分 |
POST /files/upload | 20リクエスト/分 |
POST /files/upload-complete | 20リクエスト/分 |
GET /jobs/:jobId | 120リクエスト/分 |
POST /user/create-space | 60リクエスト/分 |
POST /spaces/update-privacy | 60リクエスト/分 |
エンティティCRUD(/spaces/:spaceId/entities*) | 300リクエスト/分 |
クイックスタート
フローの概要
- APIキーを取得(ユーザーアカウントキーまたはOAuthログイン)
- 署名付きアップロードURLをリクエスト
- ファイルをS3にアップロード
- アップロードを確認し、必要に応じて非同期処理の完了をポーリング
- スペースを作成し、エンティティとゲートを管理
完全な統合例
const API_BASE_URL = "https://api-staging.arrival.space/api";
const VERSION = "v1";
/**
* Poll job status until completion or failure
* Used for async operations like zip file processing or large file copying
* Provides real-time progress updates during processing
*/
async function pollJobStatus(apiKey, jobId, onProgress = null) {
const pollUrl = `${API_BASE_URL}/${VERSION}/jobs/${jobId}`;
return new Promise((resolve, reject) => {
const poll = async () => {
try {
const res = await fetch(pollUrl, {
headers: { Authorization: `Bearer ${apiKey}` }
});
const data = await res.json();
const job = data.data;
// Optional progress callback
if (onProgress) {
onProgress(job.progress, job.message);
}
if (job.job_status === "completed") {
resolve(job.result.resource_key);
} else if (job.job_status === "failed") {
reject(new Error(job.error || "Job failed"));
} else {
// Still processing, poll again in 2 seconds
setTimeout(poll, 2000);
}
} catch (e) {
reject(e);
}
};
poll();
});
}
/**
* Upload a file to Arrival Space and create a space
*/
async function uploadFileToArrivalSpace(apiKey, file, spaceTitle, spaceDescription = "") {
// Step 1: Request presigned upload URL
const uploadRequest = await fetch(`${API_BASE_URL}/${VERSION}/files/upload`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
file_name: file.name,
file_size: file.size,
content_type: file.type || "application/octet-stream"
})
});
if (!uploadRequest.ok) {
throw new Error("Failed to get upload URL");
}
const { data } = await uploadRequest.json();
const { params } = data;
// Step 2: Upload file to S3
const s3Upload = await fetch(params.url, {
method: params.method,
body: file,
headers: params.headers
});
if (!s3Upload.ok) {
throw new Error("Failed to upload file to S3");
}
// Step 3: Confirm upload
const fileUrl = params.url.split("?")[0]; // Remove query params
const confirmRequest = await fetch(`${API_BASE_URL}/${VERSION}/files/upload-complete`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
status: "success",
extra_info: {
file_url: fileUrl
}
})
});
if (!confirmRequest.ok) {
throw new Error("Failed to confirm upload");
}
const confirmData = await confirmRequest.json();
let resource_key;
// Step 3b: Handle async processing for large files (zip files or files over 1.5GB)
if (confirmData.status === "processing" && confirmData.data.job_id) {
console.log("Large file detected - polling for completion...");
resource_key = await pollJobStatus(apiKey, confirmData.data.job_id, (progress, message) => {
console.log(`Processing: ${progress}% - ${message}`);
});
} else {
// Immediate response for small files (under 1.5GB, non-zip)
resource_key = confirmData.data.resource_key;
}
// Step 4: Create space with uploaded file
const createSpaceRequest = await fetch(`${API_BASE_URL}/${VERSION}/user/create-space`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
space_data: {
title: spaceTitle,
description: spaceDescription,
resource_key: resource_key,
}
})
});
if (!createSpaceRequest.ok) {
throw new Error("Failed to create space");
}
return await createSpaceRequest.json();
}
// Usage
const apiKey = "your_api_key_here";
const file = document.getElementById("fileInput").files[0];
uploadFileToArrivalSpace(apiKey, file, "My New Space", "Optional description")
.then(result => console.log("Space created:", result.data))
.catch(error => console.error("Upload failed:", error));
APIリファレンス
アップロード
アップロードURLのリクエスト
POST /files/upload
ファイルアップロード用の署名付き S3 URL を生成します。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
file_name | string | はい | ファイル名 |
file_size | number | はい | ファイルサイズ(バイト) |
content_type | string | はい | MIMEタイプ |
レスポンス
{
"status": "ok",
"data": {
"upload_type": "presigned_url",
"params": {
"url": "https://s3.amazonaws.com/bucket/...?X-Amz-Signature=...",
"method": "PUT",
"headers": { "Content-Type": "application/zip" }
}
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/files/upload`, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
file_name: "example.zip",
file_size: 1048576000,
content_type: "application/zip"
})
});
S3へのアップロード
署名付きURLを受け取ったら、返されたパラメーターを使用してファイルをS3に直接アップロードします:
fetch(params.url, {
method: params.method,
headers: params.headers,
body: file,
});
アップロードの確認
POST /files/upload-complete
ファイルがS3に正常にアップロードされた後に呼び出します。アップロードの完了を確認し、後でスペースやその他のエンティティを作成するために使用できるリソースキーを返します。
.zip ファイルまたは 1.5GB を超えるファイルの場合、このエンドポイントはリソースキーの代わりにジョブIDを返します。処理が完了して最終的な resource_key を取得するには、ジョブステータスエンドポイントをポーリングする必要があります。詳細はジョブステータスの取得を参照してください。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
status | string | はい | アップロード完了後に "success" を使用 |
extra_info.file_url | string | はい | S3にアップロードされたファイルのURL |
レスポンス(小さいファイル)
1.5GB未満のファイル(非zip)の場合、リソースキーを即座に返します:
{
"status": "confirmed",
"data": {
"resource_key": "api-uploads/...ply"
}
}
レスポンス(大きいファイル - 非同期処理)
.zip ファイルまたは1.5GBを超えるファイルの場合、ポーリング用のジョブIDを返します:
{
"status": "processing",
"message": "Large file queued for processing",
"data": {
"job_id": "a1b2c3d4e5f6...",
"poll_url": "/api/v1/jobs/a1b2c3d4e5f6..."
}
}
| フィールド | 説明 |
|---|---|
status | "processing" は非同期ジョブが開始されたことを示します |
job_id | ジョブステータスをポーリングするための一意の識別子 |
poll_url | ポーリング用の便利なURL |
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/files/upload-complete`, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
status: "success",
extra_info: {
file_url: "https://s3.amazonaws.com/bucket/uploads/example.zip"
}
})
});
ジョブ
ジョブステータスの取得
GET /jobs/:jobId
非同期ジョブ(zipファイル処理や大きなファイルのコピーなど)のステータスを確認するためにこのエンドポイントをポーリングします。リアルタイムの進捗更新を提供します。job_status が "completed" または "failed" になるまでポーリングを続けてください。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
URLパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
jobId | string | はい | upload-complete から返されたジョブID |
レスポンス
{
"status": "ok",
"data": {
"job_id": "a1b2c3d4e5f6...",
"job_status": "processing",
"progress": 45,
"message": "Copying file... 45% (2.3 GB / 5.0 GB)",
"result": null,
"error": null,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:31:30.000Z"
}
}
完了したジョブの例:
{
"status": "ok",
"data": {
"job_id": "a1b2c3d4e5f6...",
"job_status": "completed",
"progress": 100,
"message": "File copied successfully",
"result": {
"resource_key": "api-uploads/..."
},
"error": null,
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:32:15.000Z"
}
}
ジョブステータスの値
| ステータス | 説明 |
|---|---|
queued | ジョブは処理待ちです |
processing | ジョブは現在処理中です |
completed | ジョブが正常に完了しました — result.resource_key が利用可能です |
failed | ジョブが失敗しました — 詳細は error フィールドを確認してください |
レスポンスフィールド
| フィールド | 型 | 説明 |
|---|---|---|
job_id | string | 一意のジョブ識別子 |
job_status | string | ジョブの現在のステータス |
progress | number | 進捗率(0-100)、処理中にリアルタイムで更新されます |
message | string | 進捗の詳細を含む人間が読めるステータスメッセージ(例:「Copying file... 45% (2.3 GB / 5.0 GB)」) |
result | object | 完了時の結果データ(resource_key を含む) |
error | string | ジョブが失敗した場合のエラーメッセージ |
created_at | string | ジョブ作成時のISOタイムスタンプ |
updated_at | string | 最終ステータス更新のISOタイムスタンプ |
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${api_key}` }
});
zipファイルと大きなファイル(1.5GB超)の両方について、progress と message フィールドはファイルの処理中にリアルタイムで更新されます。1〜2秒ごとにこのエンドポイントをポーリングして、以下を含むライブ進捗の更新を取得できます:
- 完了率
- 処理済みデータ量(MBまたはGB)
- 現在の操作(例:「Copying file...」、「Extracting zip...」、「Uploading files...」)
例:ジョブ完了のポーリング
async function pollJobStatus(apiKey, jobId) {
const pollUrl = `${API_BASE_URL}/${VERSION}/jobs/${jobId}`;
return new Promise((resolve, reject) => {
const poll = async () => {
try {
const res = await fetch(pollUrl, {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
const data = await res.json();
const job = data.data;
console.log(`Progress: ${job.progress}% - ${job.message}`);
if (job.job_status === 'completed') {
resolve(job.result.resource_key);
} else if (job.job_status === 'failed') {
reject(new Error(job.error));
} else {
// Still processing, poll again in 2 seconds
setTimeout(poll, 2000);
}
} catch (e) {
reject(e);
}
};
poll();
});
}
ジョブ一覧
GET /jobs
認証されたユーザーのすべてのジョブを一覧表示します。複数の非同期操作を追跡するのに便利です。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
レスポンス
{
"status": "ok",
"data": {
"jobs": [
{
"job_id": "a1b2c3d4e5f6...",
"type": "zip-processing",
"job_status": "completed",
"progress": 100,
"message": "Zip file processed successfully",
"created_at": "2025-01-15T10:30:00.000Z",
"updated_at": "2025-01-15T10:32:15.000Z"
},
{
"job_id": "b2c3d4e5f6a1...",
"type": "file-processing",
"job_status": "processing",
"progress": 65,
"message": "Copying file... 65% (3.25 GB / 5.0 GB)",
"created_at": "2025-01-15T11:00:00.000Z",
"updated_at": "2025-01-15T11:01:30.000Z"
}
],
"count": 1
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/jobs`, {
headers: { Authorization: `Bearer ${api_key}` }
});
ジョブは1時間後に自動的にクリーンアップされます。
スペース
スペースの作成
POST /user/create-space
アップロードされたファイルのキーを使用して、Arrival Space に新しいスペースを作成します。アップロードの確認後にこのエンドポイントを呼び出してください。
スペースタイプ
スペースには2つのタイプがあります:
| タイプ | 説明 |
|---|---|
"infinite" | デフォルト。 ルームアーキテクチャのないオープンスペース。3Dコンテンツはオープンな環境に浮遊します。静的ゲートフレームは作成されません。個々の3Dモデルを展示するのに最適です。 |
"hub" | アーキテクチャ付きのルーム — 壁、床、天井、およびルーム周囲に配置された 7つの静的ゲートフレーム。他のスペースへのポータルを含むホームスペースの作成に最適です。 |
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
title | string | いいえ | スペースのタイトル(省略時はファイル名から自動検出) |
description | string | いいえ | スペースの説明 |
resource_key | string | いいえ | upload-complete からのリソースキー。省略すると空のスペースが作成されます |
space_type | string | いいえ | "hub" または "infinite"。デフォルト:"infinite" |
レスポンス
{
"status": "ok",
"message": "Space created successfully",
"data": {
"space_url": "https://arrival.space/12345678_9012",
"title": "My New Space",
"space_type": "hub"
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/user/create-space`, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
space_data: {
title: "My New Space",
description: "Optional description",
resource_key: "api-uploads/...ply",
space_type: "hub"
}
})
});
スペースのプライバシー更新
POST /spaces/update-privacy
既存のスペースのプライバシー設定を更新します。スペースの所有権が必要です。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
spaceId | string | はい | スペースの一意の識別子(スペースURLに含まれる) |
roomPrivacy | string | はい | プライバシー設定。"Public"、"Private"、"Link Only" のいずれか |
informFollowers | boolean | いいえ | true の場合、スペースが「Public」に設定された時にフォロワーに通知されます |
プライバシーオプション
| オプション | 説明 |
|---|---|
Public | スペースは公開され、検索可能です。フォロワーに通知できます。 |
Private | スペースは非公開で、検索不可です。オーナーのみが直接リンクでアクセスできます。 |
Link Only | スペースは直接リンクでアクセスできますが、公開リストには表示されません。Pro サブスクリプションが必要です。 |
レスポンス
成功:
{
"status": "ok",
"message": "Space privacy updated successfully"
}
エラーレスポンス:
// 404 - Space not found
{
"status": "error",
"message": "Space not found"
}
// 403 - Not authorized
{
"status": "error",
"message": "You are not authorized to update the privacy of this space"
}
// 400 - Invalid privacy option
{
"status": "error",
"message": "Invalid privacy option"
}
// 400 - Pro required for Link Only
{
"status": "error",
"message": "You need to be a Pro user to set a space to Link Only"
}
リクエスト例
async function updateSpacePrivacy(apiKey, spaceId, privacy, informFollowers = false) {
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/update-privacy`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
spaceId: spaceId,
roomPrivacy: privacy,
informFollowers: informFollowers
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return await response.json();
}
// Usage
updateSpacePrivacy("your_api_key", "12345678_9012", "Public", true)
.then(result => console.log("Privacy updated:", result))
.catch(error => console.error("Update failed:", error));
エンティティ
エンティティはスペース内のオブジェクト(3Dモデル、ポータル、メディアなど)です。これらのエンドポイントにより、所有するスペース内のエンティティの作成、読み取り、更新、削除が可能です。
すべてのエンティティエンドポイントには Authorization: Bearer <api_key> ヘッダーとスペースの所有権が必要です。
エンティティの作成
POST /spaces/:spaceId/entities
スペース内に新しいエンティティを作成します。resource_key が指定された場合、アップロードされたファイルから自動検出されたデフォルト値(ファイルタイプに基づくスケール、回転)でエンティティが作成されます。省略された場合、空のエンティティが作成されます。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
resource_key | string | いいえ | upload-complete からの S3 キー。ファイルベースのエンティティ作成を自動検出のスケール/回転でトリガーします |
entity_id | string | いいえ | カスタムエンティティID。省略時は自動生成されます |
entity_type | string | いいえ | エンティティタイプ(例:"UserModelEntity"、"Simple"、"Gate") |
entity_data | object | いいえ | エンティティデータ。resource_key 指定時は自動検出されたデフォルトとマージされます |
レスポンス(201):
{
"status": "ok",
"data": {
"entity_id": "user-model-91de72809185c9fa",
"entity_type": "UserModelEntity",
"entity_data": {
"glbUrl": "https://s3.amazonaws.com/...",
"scale": [2, 2, 2],
"rotation": { "x": 0, "y": 0, "z": 0 },
"position": [0, 1, 0]
}
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities`, {
method: "POST",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
resource_key: "api-uploads/...glb",
entity_id: "my-custom-id",
entity_type: "UserModelEntity",
entity_data: {
scale: [2, 2, 2],
position: [0, 1, 0]
}
})
});
エンティティ一覧
GET /spaces/:spaceId/entities
スペース内のすべてのエンティティを返します(内部の RoomInfo エンティティを除く)。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
クエリパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
limit | number | いいえ | 返すエンティティの最大数(デフォルト50、最大200) |
cursor | string | いいえ | nextCursor からのページネーションカーソル |
レスポンス:
{
"status": "ok",
"data": {
"entities": [
{
"entity_id": "user-model-91de72809185c9fa",
"entity_type": "UserModelEntity",
"entity_data": { "glbUrl": "...", "scale": 1, "rotation": { "x": 0, "y": 0, "z": 0 } },
"entity_state": {},
"created_date": "2026-01-27T12:00:00.000Z",
"change_date": "2026-01-27T12:00:00.000Z"
}
],
"count": 1,
"total": 1,
"nextCursor": null,
"hasMore": false
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities?limit=50`, {
headers: { Authorization: `Bearer ${api_key}` }
});
エンティティの取得
GET /spaces/:spaceId/entities/:entityId
IDで単一のエンティティを返します。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
パスパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
spaceId | string | はい | スペースID(スペースURLから) |
entityId | string | はい | エンティティID |
レスポンス:
{
"status": "ok",
"data": {
"entity_id": "user-model-91de72809185c9fa",
"entity_type": "UserModelEntity",
"entity_data": { "glbUrl": "...", "scale": 1, "rotation": { "x": 0, "y": 0, "z": 0 } },
"entity_state": {},
"created_date": "2026-01-27T12:00:00.000Z",
"change_date": "2026-01-27T12:00:00.000Z"
}
}
エラー(404):
{ "status": "error", "message": "Entity not found" }
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${entityId}`, {
headers: { Authorization: `Bearer ${api_key}` }
});
エンティティの更新
PUT /spaces/:spaceId/entities/:entityId
マージセマンティクスを使用してエンティティを更新します:指定されたフィールドが既存の値を上書きし、未指定のフィールドは保持されます。エンティティの state も保持されます。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
Content-Type | application/json | はい | JSONリクエストボディ |
ボディパラメーター:
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
entity_data | object | はい | 既存のエンティティにマージするフィールド |
レスポンス:
{
"status": "ok",
"data": {
"entity_id": "user-model-91de72809185c9fa",
"entity_type": "UserModelEntity",
"entity_data": {
"glbUrl": "https://s3.amazonaws.com/...",
"scale": [3, 3, 3],
"rotation": { "x": 0, "y": 0, "z": 0 },
"label": "My Label"
}
}
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${entityId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
entity_data: {
scale: [3, 3, 3],
label: "My Label"
}
})
});
エンティティの削除
DELETE /spaces/:spaceId/entities/:entityId
スペースからエンティティを完全に削除します。
リクエスト
ヘッダー:
| ヘッダー | 値 | 必須 | 説明 |
|---|---|---|---|
Authorization | Bearer <api_key> | はい | ユーザーのAPIキー |
パスパラメーター:
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
spaceId | string | はい | スペースID(スペースURLから) |
entityId | string | はい | エンティティID |
レスポンス:
{
"status": "ok",
"data": { "message": "Entity deleted successfully" }
}
リクエスト例
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${entityId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${api_key}` }
});
エンティティ管理の例
async function manageEntities(apiKey, spaceId, resourceKey) {
const headers = {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
};
const base = `${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities`;
// Create entity from uploaded file
const createRes = await fetch(base, {
method: "POST",
headers,
body: JSON.stringify({
resource_key: resourceKey,
entity_data: { scale: [2, 2, 2] }
})
});
const { data: created } = await createRes.json();
console.log("Created:", created.entity_id);
// List all entities
const listRes = await fetch(base, { headers });
const { data: list } = await listRes.json();
console.log("Entities in space:", list.count);
// Update entity
await fetch(`${base}/${created.entity_id}`, {
method: "PUT",
headers,
body: JSON.stringify({ entity_data: { label: "Updated" } })
});
// Delete entity
await fetch(`${base}/${created.entity_id}`, {
method: "DELETE",
headers
});
}
ゲート
ゲートはスペースを相互にリンクするポータルです。2種類あります:
-
静的ゲート — hub ルーム周囲に配置された7つの固定ポータルフレーム。hub スペースが作成されると(
POST /user/create-spaceでspace_type: "hub")自動的に作成されます。コンテンツを設定したり空のままにしたりできますが、追加や削除はできません。静的ゲートは infinite スペースには存在しません — infinite スペースにはルームアーキテクチャがないため、ゲートフレームを表示する場所がありません。 -
動的ゲート — 3D空間内の任意の場所に自由に配置できるポータル。hub と infinite の両方のスペースで任意の数を作成できます。
両方のゲートタイプは、上記のエンティティエンドポイントを通じて管理されます。
静的ゲートは hub スペース(hideArchitecture が false の場合)でのみ意味があります。space_type: "infinite"(デフォルト)でスペースを作成した場合、ルームアーキテクチャが非表示になるため静的ゲートは作成されません。infinite スペースでポータルが必要な場合は動的ゲートを使用してください。
静的ゲート(タイプ:Gate)
静的ゲートは hub スペースで自動的に作成されます。3D位置はクライアントによって固定されています — コンテンツ(タイトル、リンク、説明、サムネイルなど)のみを制御できます。
すべての hub スペースには同じ7つのゲートエンティティIDがあります。これらの固定UUIDを使用して、PUT /spaces/:spaceId/entities/:entityId 経由でゲートに直接アクセスできます:
| ゲート | エンティティID |
|---|---|
| 0 | 9769e4b4-e5d9-4286-9353-c2c66b158347 |
| 1 | 1aef8fd4-6447-4e04-969a-4669c11dd52e |
| 2 | 75f16b87-5314-4cdf-a8d5-bb2a8292b687 |
| 3 | 608f2483-802b-4fa7-95bb-920bed1431ad |
| 4 | fcc35518-309c-47e1-8d93-d939d6bca475 |
| 5 | 404c7fe3-26f1-4fb6-bac7-a50bf1f3cb1a |
| 6 | b9a67fa0-00ce-401d-94c2-4045c4aaec35 |
例 — ゲート3を別のスペースにリンクする:
const GATE_IDS = [
"9769e4b4-e5d9-4286-9353-c2c66b158347", // Gate 0
"1aef8fd4-6447-4e04-969a-4669c11dd52e", // Gate 1
"75f16b87-5314-4cdf-a8d5-bb2a8292b687", // Gate 2
"608f2483-802b-4fa7-95bb-920bed1431ad", // Gate 3
"fcc35518-309c-47e1-8d93-d939d6bca475", // Gate 4
"404c7fe3-26f1-4fb6-bac7-a50bf1f3cb1a", // Gate 5
"b9a67fa0-00ce-401d-94c2-4045c4aaec35", // Gate 6
];
// Internal link — point gate 3 to another space
await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${GATE_IDS[3]}`, {
method: "PUT",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
entity_data: {
title: "My Portal",
link: "42485456_5076",
platform: "arrival.space",
description: "Step through to see my gallery"
}
})
});
// External link — point gate 0 to a web page
// Set videoURL to the same URL so the page renders on the gate surface
await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${GATE_IDS[0]}`, {
method: "PUT",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
entity_data: {
title: "Visit Our Website",
link: "https://example.com",
platform: "<auto-detect>",
videoURL: "https://example.com"
}
})
});
ゲートコンテンツフィールド(entity_data):
| フィールド | 型 | 説明 |
|---|---|---|
title | string | ポータルフレームに表示されるゲートタイトル |
link | string | 内部リンクの場合はスペースID(例:"42485456_5076")、または外部リンクの場合は完全なURL(例:"https://example.com")。リンクなしの場合は空にしてください |
platform | string | 内部スペースリンクの場合は "arrival.space"、外部URLの場合は "<auto-detect>"。link のタイプと一致する必要があります |
description | string | 説明テキスト(HTMLをサポート) |
videoURL | string | ゲート表面に表示される画像URLまたはWebページURL。外部リンクの場合、ゲート上にページをレンダリングするには link と同じURLを設定してください |
logoURL | string | ロゴ画像URL(ゲート上の小さいアイコン) |
logoLinkURL | string | ロゴクリック時に開くURL |
content360Enabled | boolean | 360度コンテンツモードを有効にする |
contentSynchronized | boolean | ユーザー間でコンテンツを同期する |
openAsTab | boolean | ナビゲーションの代わりに新しいブラウザタブでリンクを開く |
enableWebStream | boolean | Webストリーミングを有効にする |
enableWebProxy | boolean | Webプロキシを有効にする |
embeddedEnabled | boolean | 埋め込みコンテンツを有効にする |
screenSelect | number | スクリーン選択インデックス(デフォルト:0) |
マージセマンティクスを活用してください — 変更したいフィールドのみを送信するだけで済みます。ブーリアンフラグやその他のフィールドはデフォルト値を保持します。
動的ゲート(タイプ:DynamicGate)
動的ゲートは静的ゲートと同じコンテンツフィールドに加えて、3D位置、回転、スケールを定義する dynamicGate オブジェクトを持ちます。任意の数を作成できます。
動的ゲートは POST /spaces/:spaceId/entities で作成します。
追加フィールド:
| フィールド | 型 | 説明 |
|---|---|---|
dynamicGate | object | 動的ゲートに必須。3Dトランスフォームデータを含みます |
dynamicGate.position | {x, y, z} | ワールド位置 |
dynamicGate.rotation | {x, y, z} | オイラー回転(度) |
dynamicGate.scale | {w, h} | 幅と高さのスケール(デフォルト:{w: 1, h: 1}) |
dynamicGate.frameless | boolean | ポータルフレームを非表示にする(false = フレームを表示) |
dynamicGate.hidden | boolean | ゲートを完全に非表示にする |
例 — 動的ゲートの作成:
await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
entity_id: "my-dynamic-gate",
entity_type: "DynamicGate",
entity_data: {
dynamicGate: {
position: { x: 5.0, y: 0.0, z: -3.0 },
rotation: { x: 0, y: 45, z: 0 },
frameless: false,
hidden: false,
scale: { w: 1, h: 1 }
},
title: "Side Gallery",
link: "42485456_5076",
platform: "arrival.space",
description: "A freely-placed portal"
}
})
});
ゲートのクリア
静的ゲートをクリアする(コンテンツを削除してスロットは保持する)には、空の値で更新します:
await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${gateId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
entity_data: {
title: "",
link: "",
description: "",
videoURL: "",
logoURL: "",
logoLinkURL: ""
}
})
});
動的ゲートを完全に削除するには、DELETE /spaces/:spaceId/entities/:entityId を使用してください。
静的ゲートを削除しないでください — これらはルーム構造の一部です。コンテンツのみをクリアしてください。静的ゲートを削除すると、API経由では復元できない空のスロットが残ります。
エラーハンドリング
| ステータスコード | 説明 |
|---|---|
200 | OK — リクエストが成功しました |
201 | Created — リソースが正常に作成されました(例:エンティティ) |
202 | Accepted — 非同期ジョブが開始されました(完了をポーリングしてください) |
204 | No Content — 非同期ジョブがヘッダーを更新しました |
400 | Bad Request — 無効なパラメーター |
401 | Unauthorized — 無効または期限切れのAPIキー |
403 | Forbidden — 権限が不足しています |
404 | Not Found — リソースまたはジョブが見つかりません |
413 | Payload Too Large — ファイルがサイズ制限を超えています |
500 | Internal Server Error |
507 | Insufficient Storage — ユーザーのストレージクォータを超過しています |
注意事項
- APIキーは個々のユーザーアカウントに紐付けられています
- アップロードされたファイルは、APIキー保有者が所有するスペースを作成します
- 署名付きURLは限られた時間で期限切れになります — 速やかにアップロードしてください
- APIキーを安全に保管し、クライアントサイドのコードに公開しないでください