Zum Hauptinhalt springen

REST API

Zuletzt aktualisiert:27.01.2026Version2.1

Überblick

Die Arrival Space REST API ermöglicht es Drittanbieter-Anwendungen, Benutzer zu authentifizieren, Assets über vorsignierte URLs hochzuladen, asynchrone Verarbeitungs-Jobs zu verfolgen, Spaces zu erstellen und zu verwalten, Entities zu verwalten und Gates zu konfigurieren.

Funktionen

  • Assets mit vorsignierten S3-URLs hochladen und Verarbeitung bestätigen
  • Asynchrone Verarbeitungs-Jobs mit Echtzeit-Fortschritt verfolgen
  • Spaces aus Uploads erstellen und Datenschutzeinstellungen aktualisieren
  • Entities innerhalb von Spaces erstellen, lesen, aktualisieren und löschen
  • Statische und dynamische Gates über Entity-Endpunkte konfigurieren
vorsicht

Stellen Sie sicher, dass Sie genügend verfügbaren Speicherplatz haben, bevor Sie den Upload starten. Andernfalls kann der Upload fehlschlagen.


Basis-URL & Versionierung

const API_BASE_URL = "https://api-staging.arrival.space/api";
const VERSION = "v1";

Alle Endpunkte sollten mit ${API_BASE_URL}/${VERSION} vorangestellt werden.

hinweis

Der API-Endpunkt kann sich in Zukunft ändern. Wenn Sie Verbindungsprobleme haben, wenden Sie sich bitte an unser Support-Team.


Authentifizierung

Alle API-Anfragen erfordern einen Bearer-Token im Authorization-Header:

Authorization: Bearer <api_key>

Option 1: Benutzer-Account API-Schlüssel

  1. Melden Sie sich bei Arrival Space an
  2. Navigieren Sie zu Ihren Kontoeinstellungen
  3. Generieren oder kopieren Sie Ihren API-Schlüssel

Option 2: OAuth 2.0 Login (für native Apps)

Native Anwendungen können OAuth 2.0 Authorization Code Flow mit PKCE verwenden, damit sich Benutzer über ihr Arrival.Space-Konto anmelden können (Google-Anmeldung oder E-Mail/Passwort). Dies ist der empfohlene Ansatz für Drittanbieter-Integrationen, bei denen Benutzer API-Schlüssel nicht manuell kopieren müssen.

Der OAuth-Flow verwendet Standard-Endpunkte und gibt einen API-Schlüssel als Access-Token zurück — denselben Schlüssel, der in den Kontoeinstellungen des Benutzers sichtbar ist. Das bedeutet, dass der Token Server-Neustarts übersteht und identisch über alle API-Endpunkte funktioniert.

OAuth-Endpunkte:

EndpunktBeschreibung
GET /.well-known/oauth-authorization-serverOAuth 2.0 Server-Metadaten (Auto-Discovery)
POST /registerDynamische Client-Registrierung (RFC 7591)
GET /authorizeAutorisierungs-Endpunkt — leitet zur Login-Seite weiter
POST /tokenToken-Austausch — gibt den Access-Token zurück
POST /revokeToken-Widerruf

Ablauf-Übersicht:

  1. Client registrieren (einmalig oder beim ersten Start):

    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
  2. PKCE-Challenge generieren und Benutzer zur Autorisierung weiterleiten:

    // 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));

    Der Benutzer sieht die Arrival.Space-Anmeldeseite mit Google-Anmeldung und E-Mail/Passwort-Optionen.

  3. Callback verarbeiten — nach der Anmeldung leitet der Browser zu Ihrer redirect_uri mit ?code=...&state=... weiter:

    // 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;
  4. Token verwenden — es ist ein Standard-API-Schlüssel, verwenden Sie ihn wie jeden anderen:

    const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces`, {
    headers: { Authorization: `Bearer ${apiKey}` }
    });
Token-Details
  • Der vom OAuth-Flow zurückgegebene access_token ist der tatsächliche API-Schlüssel des Benutzers — derselbe, der in seinen Kontoeinstellungen angezeigt wird
  • Token laufen nicht ab (kein expires_in-Limit) — sie bestehen, bis der Benutzer den Schlüssel widerruft
  • Es wird kein Refresh-Token benötigt, da der Access-Token nicht abläuft
  • Wenn der Benutzer bereits einen API-Schlüssel hat, wird der vorhandene Schlüssel zurückgegeben (keine Duplikate erstellt)
Reverse-Proxy-Konfiguration

Wenn Ihr Server hinter einem Reverse-Proxy (Apache, nginx) sitzt, stellen Sie sicher, dass /.well-known/oauth-authorization-server an das Node.js-Backend weitergeleitet wird. Wenn die Discovery nicht verfügbar ist, können Clients die Endpunkte direkt verwenden: /authorize, /token, /register.

MCP-Protokoll-Unterstützung

Die OAuth-Endpunkte sind auch kompatibel mit dem Model Context Protocol (MCP). MCP-Clients wie Claude Desktop können sich direkt über https://api-dev.arrival.space/api/v1/mcp verbinden — der OAuth-Flow wird automatisch vom MCP-Client übernommen.


Konventionen

  • Antworten verwenden einen gemeinsamen Umschlag mit status, data und manchmal message oder error
  • Zeitstempel sind ISO 8601 Zeichenketten (z.B. 2025-01-15T10:30:00.000Z)
  • Einige Operationen sind asynchron und geben eine job_id zurück, die Sie über GET /jobs/:jobId abfragen, bis job_status completed oder failed ist
  • resource_key identifiziert ein hochgeladenes Asset und wird verwendet, um Spaces oder Entities zu erstellen
  • spaceId ist der eindeutige Bezeichner in der Space-URL (z.B. https://arrival.space/12345678_9012)
  • entity_id identifiziert eine bestimmte Entity innerhalb eines Space

Ratenbegrenzungen

Limits werden pro API-Schlüssel durchgesetzt (Fallback auf IP, wenn kein Schlüssel angegeben). Eine 429-Antwort enthält einen Retry-After-Header und ein retry_after_seconds-Feld im Body.

BereichLimit
Alle API-Endpunkte300 Anfragen/Min
POST /files/upload20 Anfragen/Min
POST /files/upload-complete20 Anfragen/Min
GET /jobs/:jobId120 Anfragen/Min
POST /user/create-space60 Anfragen/Min
POST /spaces/update-privacy60 Anfragen/Min
Entity-CRUD (/spaces/:spaceId/entities*)300 Anfragen/Min

Schnellstart

Ablauf-Zusammenfassung

  1. API-Schlüssel beschaffen (Benutzer-Account-Schlüssel oder OAuth-Login)
  2. Vorsignierte Upload-URL anfordern
  3. Datei auf S3 hochladen
  4. Upload bestätigen und bei Bedarf auf asynchrone Verarbeitungsfertigstellung abfragen
  5. Space erstellen und Entities sowie Gates verwalten

Vollständiges Integrationsbeispiel

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-Referenz

Uploads

Upload-URL anfordern

POST /files/upload

Generiert eine vorsignierte S3-URL für den Datei-Upload.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

FeldTypErforderlichBeschreibung
file_namestringJaName der Datei
file_sizenumberJaDateigröße in Bytes
content_typestringJaMIME-Typ
Antwort
{
"status": "ok",
"data": {
"upload_type": "presigned_url",
"params": {
"url": "https://s3.amazonaws.com/bucket/...?X-Amz-Signature=...",
"method": "PUT",
"headers": { "Content-Type": "application/zip" }
}
}
}
Beispielanfrage
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"
})
});
Auf S3 hochladen

Sobald Sie die vorsignierte URL erhalten haben, verwenden Sie die zurückgegebenen Parameter, um die Datei direkt auf S3 hochzuladen:

fetch(params.url, {
method: params.method,
headers: params.headers,
body: file,
});

Upload bestätigen

POST /files/upload-complete

Rufen Sie dies auf, nachdem die Datei erfolgreich auf S3 hochgeladen wurde. Bestätigt den Abschluss des Uploads und gibt den Resource-Key zurück, den Sie später verwenden können, um einen Space oder andere Entities zu erstellen.

Asynchrone Verarbeitung für große Dateien

Für .zip-Dateien oder Dateien über 1,5 GB gibt dieser Endpunkt eine Job-ID anstelle des Resource-Key direkt zurück. Sie müssen den Job-Status-Endpunkt abfragen, um den endgültigen resource_key zu erhalten, sobald die Verarbeitung abgeschlossen ist. Siehe Job-Status abrufen für Details.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

FeldTypErforderlichBeschreibung
statusstringJaVerwenden Sie "success" nach Abschluss des Uploads
extra_info.file_urlstringJaURL der hochgeladenen Datei in S3
Antwort (Kleine Dateien)

Für Dateien unter 1,5 GB (kein Zip) wird der Resource-Key sofort zurückgegeben:

{
"status": "confirmed",
"data": {
"resource_key": "api-uploads/...ply"
}
}
Antwort (Große Dateien - Asynchrone Verarbeitung)

Für .zip-Dateien oder Dateien über 1,5 GB wird eine Job-ID zum Abfragen zurückgegeben:

{
"status": "processing",
"message": "Large file queued for processing",
"data": {
"job_id": "a1b2c3d4e5f6...",
"poll_url": "/api/v1/jobs/a1b2c3d4e5f6..."
}
}
FeldBeschreibung
status"processing" zeigt an, dass ein asynchroner Job gestartet wurde
job_idEindeutiger Bezeichner zum Abfragen des Job-Status
poll_urlKomfort-URL zum Abfragen
Beispielanfrage
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"
}
})
});

Jobs

Job-Status abrufen

GET /jobs/:jobId

Fragen Sie diesen Endpunkt ab, um den Status eines asynchronen Jobs zu überprüfen (z.B. Zip-Datei-Verarbeitung oder Kopieren großer Dateien). Der Endpunkt liefert Echtzeit-Fortschrittsaktualisierungen. Fragen Sie weiter ab, bis job_status "completed" oder "failed" ist.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers

URL-Parameter:

ParameterTypErforderlichBeschreibung
jobIdstringJaDie von upload-complete zurückgegebene Job-ID
Antwort
{
"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"
}
}

Beispiel für abgeschlossenen Job:

{
"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"
}
}
Job-Status-Werte
StatusBeschreibung
queuedJob wartet auf Verarbeitung
processingJob wird aktuell verarbeitet
completedJob erfolgreich abgeschlossen — result.resource_key ist verfügbar
failedJob fehlgeschlagen — Details im error-Feld
Antwort-Felder
FeldTypBeschreibung
job_idstringEindeutiger Job-Bezeichner
job_statusstringAktueller Status des Jobs
progressnumberFortschritt in Prozent (0-100), wird während der Verarbeitung in Echtzeit aktualisiert
messagestringMenschenlesbare Statusmeldung mit Fortschrittsdetails (z.B. "Copying file... 45% (2.3 GB / 5.0 GB)")
resultobjectErgebnisdaten bei Abschluss (enthält resource_key)
errorstringFehlermeldung bei fehlgeschlagenem Job
created_atstringISO-Zeitstempel der Job-Erstellung
updated_atstringISO-Zeitstempel der letzten Statusaktualisierung
Beispielanfrage
const response = await fetch(`${API_BASE_URL}/${VERSION}/jobs/${jobId}`, {
headers: { Authorization: `Bearer ${api_key}` }
});
Fortschrittsaktualisierungen

Sowohl für Zip-Dateien als auch für große Dateien (über 1,5 GB) werden die Felder progress und message in Echtzeit aktualisiert, während die Datei verarbeitet wird. Sie können diesen Endpunkt alle 1-2 Sekunden abfragen, um Live-Fortschrittsaktualisierungen zu erhalten, die Folgendes anzeigen:

  • Prozentsatz des Fortschritts
  • Menge der verarbeiteten Daten (MB oder GB)
  • Aktuelle Operation (z.B. "Copying file...", "Extracting zip...", "Uploading files...")
Beispiel: Abfrage bis zum Job-Abschluss
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();
});
}

Jobs auflisten

GET /jobs

Listet alle Jobs des authentifizierten Benutzers auf. Nützlich zum Verfolgen mehrerer asynchroner Operationen.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Antwort
{
"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
}
}
Beispielanfrage
const response = await fetch(`${API_BASE_URL}/${VERSION}/jobs`, {
headers: { Authorization: `Bearer ${api_key}` }
});
hinweis

Jobs werden nach 1 Stunde automatisch bereinigt.


Spaces

Space erstellen

POST /user/create-space

Erstellt einen neuen Space in Arrival Space mit dem Schlüssel zur hochgeladenen Datei. Rufen Sie dies nach der Bestätigung des Uploads auf.

Space-Typen

Es gibt zwei Arten von Spaces:

TypBeschreibung
"infinite"Standard. Offener Space ohne Raumarchitektur. Der 3D-Inhalt schwebt in einer offenen Umgebung. Es werden keine statischen Gate-Rahmen erstellt. Am besten geeignet für die Präsentation einzelner 3D-Modelle.
"hub"Raum mit Architektur — Wände, Boden, Decke und 7 statische Gate-Rahmen, die um den Raum angeordnet sind. Am besten geeignet für die Erstellung eines Home-Space mit Portalen zu anderen Spaces.
Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

ParameterTypErforderlichBeschreibung
titlestringNeinSpace-Titel (wird automatisch aus dem Dateinamen erkannt, wenn weggelassen)
descriptionstringNeinSpace-Beschreibung
resource_keystringNeinResource-Key von upload-complete. Wenn weggelassen, wird ein leerer Space erstellt
space_typestringNein"hub" oder "infinite". Standard: "infinite"
Antwort
{
"status": "ok",
"message": "Space created successfully",
"data": {
"space_url": "https://arrival.space/12345678_9012",
"title": "My New Space",
"space_type": "hub"
}
}
Beispielanfrage
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"
}
})
});

Space-Datenschutz aktualisieren

POST /spaces/update-privacy

Aktualisiert die Datenschutzeinstellung eines bestehenden Space. Erfordert Besitzerrechte am Space.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

ParameterTypErforderlichBeschreibung
spaceIdstringJaDer eindeutige Bezeichner des Space (in der Space-URL zu finden)
roomPrivacystringJaDatenschutzeinstellung. Eine von: "Public", "Private", "Link Only"
informFollowersbooleanNeinWenn true, werden Follower benachrichtigt, wenn der Space auf "Public" gesetzt wird
Datenschutzoptionen
OptionBeschreibung
PublicSpace ist öffentlich sichtbar und auffindbar. Follower können benachrichtigt werden.
PrivateSpace ist privat und nicht auffindbar. Nur über Direktlink durch den Besitzer zugänglich.
Link OnlySpace ist über Direktlink zugänglich, aber nicht öffentlich gelistet. Erfordert Pro-Abonnement.
Antwort

Erfolg:

{
"status": "ok",
"message": "Space privacy updated successfully"
}

Fehlerantworten:

// 404 - Space nicht gefunden
{
"status": "error",
"message": "Space not found"
}

// 403 - Nicht autorisiert
{
"status": "error",
"message": "You are not authorized to update the privacy of this space"
}

// 400 - Ungültige Datenschutzoption
{
"status": "error",
"message": "Invalid privacy option"
}

// 400 - Pro erforderlich für Link Only
{
"status": "error",
"message": "You need to be a Pro user to set a space to Link Only"
}
Beispielanfrage
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));

Entities

Entities sind Objekte innerhalb eines Space (3D-Modelle, Portale, Medien usw.). Diese Endpunkte ermöglichen es Ihnen, Entities in Spaces, die Sie besitzen, zu erstellen, zu lesen, zu aktualisieren und zu löschen.

Alle Entity-Endpunkte erfordern den Authorization: Bearer <api_key>-Header und Space-Besitzerrechte.

Entity erstellen

POST /spaces/:spaceId/entities

Erstellt eine neue Entity in einem Space. Wenn resource_key angegeben wird, wird die Entity aus einer hochgeladenen Datei mit automatisch erkannten Standardwerten (Skalierung, Rotation basierend auf Dateityp) erstellt. Wenn weggelassen, wird eine leere Entity erstellt.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

ParameterTypErforderlichBeschreibung
resource_keystringNeinS3-Schlüssel von upload-complete. Löst dateibasierte Entity-Erstellung mit automatisch erkannter Skalierung/Rotation aus
entity_idstringNeinBenutzerdefinierte Entity-ID. Wird automatisch generiert, wenn weggelassen
entity_typestringNeinEntity-Typ (z.B. "UserModelEntity", "Simple", "Gate")
entity_dataobjectNeinEntity-Daten. Werden mit automatisch erkannten Standardwerten zusammengeführt, wenn resource_key angegeben ist

Antwort (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]
}
}
}
Beispielanfrage
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]
}
})
});

Entities auflisten

GET /spaces/:spaceId/entities

Gibt alle Entities in einem Space zurück (ausgenommen die interne RoomInfo-Entity).

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers

Query-Parameter:

ParameterTypErforderlichBeschreibung
limitnumberNeinMaximale Anzahl zurückzugebender Entities (Standard 50, Maximum 200)
cursorstringNeinPaginierungs-Cursor von nextCursor

Antwort:

{
"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
}
}
Beispielanfrage
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities?limit=50`, {
headers: { Authorization: `Bearer ${api_key}` }
});

Entity abrufen

GET /spaces/:spaceId/entities/:entityId

Gibt eine einzelne Entity anhand der ID zurück.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers

Pfad-Parameter:

ParameterTypErforderlichBeschreibung
spaceIdstringJaSpace-ID (aus der Space-URL)
entityIdstringJaEntity-ID

Antwort:

{
"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"
}
}

Fehler (404):

{ "status": "error", "message": "Entity not found" }
Beispielanfrage
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${entityId}`, {
headers: { Authorization: `Bearer ${api_key}` }
});

Entity aktualisieren

PUT /spaces/:spaceId/entities/:entityId

Aktualisiert eine Entity mit Merge-Semantik: Angegebene Felder überschreiben vorhandene Werte, nicht gesetzte Felder werden beibehalten. Der state der Entity wird ebenfalls beibehalten.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers
Content-Typeapplication/jsonJaJSON-Anfragebody

Body-Parameter:

FeldTypErforderlichBeschreibung
entity_dataobjectJaFelder, die mit der bestehenden Entity zusammengeführt werden

Antwort:

{
"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"
}
}
}
Beispielanfrage
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"
}
})
});

Entity löschen

DELETE /spaces/:spaceId/entities/:entityId

Löscht eine Entity dauerhaft aus einem Space.

Anfrage

Headers:

HeaderWertErforderlichBeschreibung
AuthorizationBearer <api_key>JaAPI-Schlüssel des Benutzers

Pfad-Parameter:

ParameterTypErforderlichBeschreibung
spaceIdstringJaSpace-ID (aus der Space-URL)
entityIdstringJaEntity-ID

Antwort:

{
"status": "ok",
"data": { "message": "Entity deleted successfully" }
}
Beispielanfrage
const response = await fetch(`${API_BASE_URL}/${VERSION}/spaces/${spaceId}/entities/${entityId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${api_key}` }
});

Beispiel zur Entity-Verwaltung

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
});
}

Gates

Gates sind Portale, die Spaces miteinander verbinden. Es gibt zwei Arten:

  • Statische Gates — 7 feste Portal-Rahmen, die um einen Hub-Raum angeordnet sind. Sie werden automatisch erstellt, wenn ein Hub-Space angelegt wird (space_type: "hub" in POST /user/create-space). Sie können sie mit Inhalt füllen oder leer lassen, aber Sie können sie nicht hinzufügen oder entfernen. Statische Gates existieren nicht in Infinite-Spaces — da Infinite-Spaces keine Raumarchitektur haben, gibt es keine Gate-Rahmen zur Anzeige.

  • Dynamische Gates — frei positionierbare Portale, die überall im 3D-Raum platziert werden können. Sie können beliebig viele davon in beiden Hub- und Infinite-Spaces erstellen.

Beide Gate-Typen werden über die oben beschriebenen Entity-Endpunkte verwaltet.

Hub vs. Infinite

Statische Gates sind nur in Hub-Spaces sinnvoll (wo hideArchitecture auf false steht). Wenn Sie einen Space mit space_type: "infinite" (Standard) erstellen, werden keine statischen Gates erzeugt, da die Raumarchitektur ausgeblendet ist. Verwenden Sie dynamische Gates, wenn Sie Portale in einem Infinite-Space benötigen.

Statische Gates (Typ: Gate)

Statische Gates werden automatisch in Hub-Spaces erstellt. Ihre 3D-Positionen werden vom Client festgelegt — Sie steuern nur deren Inhalt (Titel, Link, Beschreibung, Vorschaubild usw.).

Jeder Hub-Space hat die gleichen 7 Gate-Entity-IDs. Verwenden Sie diese festen UUIDs, um Gates direkt über PUT /spaces/:spaceId/entities/:entityId anzusprechen:

GateEntity-ID
09769e4b4-e5d9-4286-9353-c2c66b158347
11aef8fd4-6447-4e04-969a-4669c11dd52e
275f16b87-5314-4cdf-a8d5-bb2a8292b687
3608f2483-802b-4fa7-95bb-920bed1431ad
4fcc35518-309c-47e1-8d93-d939d6bca475
5404c7fe3-26f1-4fb6-bac7-a50bf1f3cb1a
6b9a67fa0-00ce-401d-94c2-4045c4aaec35

Beispiel — Gate 3 auf einen anderen Space verlinken:

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"
}
})
});

Gate-Inhaltsfelder (entity_data):

FeldTypBeschreibung
titlestringGate-Titel, der auf dem Portal-Rahmen angezeigt wird
linkstringSpace-ID für interne Links (z.B. "42485456_5076") oder eine vollständige URL für externe Links (z.B. "https://example.com"). Leer lassen für keinen Link
platformstring"arrival.space" für interne Space-Links, "<auto-detect>" für externe URLs. Muss zum link-Typ passen
descriptionstringBeschreibungstext (unterstützt HTML)
videoURLstringBild-URL oder Webseiten-URL, die auf der Gate-Oberfläche angezeigt wird. Für externe Links setzen Sie dies auf dieselbe URL wie link, um die Seite auf dem Gate darzustellen
logoURLstringLogo-Bild-URL (kleines Symbol auf dem Gate)
logoLinkURLstringURL, die geöffnet wird, wenn das Logo angeklickt wird
content360Enabledboolean360-Grad-Inhaltsmodus aktivieren
contentSynchronizedbooleanInhalt benutzerübergreifend synchronisieren
openAsTabbooleanLink in einem neuen Browser-Tab öffnen statt zu navigieren
enableWebStreambooleanWeb-Streaming aktivieren
enableWebProxybooleanWeb-Proxy aktivieren
embeddedEnabledbooleanEingebetteten Inhalt aktivieren
screenSelectnumberBildschirmauswahl-Index (Standard: 0)
tipp

Nutzen Sie die Merge-Semantik zu Ihrem Vorteil — Sie müssen nur die Felder senden, die Sie ändern möchten. Die booleschen Flags und anderen Felder behalten ihre Standardwerte.

Dynamische Gates (Typ: DynamicGate)

Dynamische Gates haben die gleichen Inhaltsfelder wie statische Gates, plus ein dynamicGate-Objekt, das ihre 3D-Position, Rotation und Skalierung definiert. Sie können beliebig viele davon erstellen.

Erstellen Sie dynamische Gates mit POST /spaces/:spaceId/entities.

Zusätzliches Feld:

FeldTypBeschreibung
dynamicGateobjectErforderlich für dynamische Gates. Enthält 3D-Transformationsdaten
dynamicGate.position{x, y, z}Weltposition
dynamicGate.rotation{x, y, z}Euler-Rotation in Grad
dynamicGate.scale{w, h}Breiten- und Höhenskalierung (Standard: {w: 1, h: 1})
dynamicGate.framelessbooleanPortal-Rahmen ausblenden (false = Rahmen anzeigen)
dynamicGate.hiddenbooleanGate vollständig ausblenden

Beispiel — Ein dynamisches Gate erstellen:

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"
}
})
});

Ein Gate leeren

Um ein statisches Gate zu leeren (seinen Inhalt entfernen, aber den Slot behalten), aktualisieren Sie es mit leeren Werten:

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: ""
}
})
});

Um ein dynamisches Gate vollständig zu entfernen, verwenden Sie DELETE /spaces/:spaceId/entities/:entityId.

vorsicht

Löschen Sie keine statischen Gates — sie sind Teil der Raumstruktur. Leeren Sie nur deren Inhalt. Das Löschen eines statischen Gates hinterlässt einen leeren Slot, der über die API nicht wiederhergestellt werden kann.


Fehlerbehandlung

StatuscodeBeschreibung
200OK — Anfrage erfolgreich
201Erstellt — Ressource erfolgreich erstellt (z.B. Entity)
202Akzeptiert — Asynchroner Job gestartet (Abfrage bis zum Abschluss)
204Kein Inhalt — Asynchrone Job-Header aktualisiert
400Fehlerhafte Anfrage — Ungültige Parameter
401Nicht autorisiert — Ungültiger oder abgelaufener API-Schlüssel
403Verboten — Unzureichende Berechtigungen
404Nicht gefunden — Ressource oder Job nicht gefunden
413Nutzlast zu groß — Datei überschreitet Größenlimit
500Interner Serverfehler
507Unzureichender Speicher — Benutzerspeicherkontingent überschritten

Hinweise

  • API-Schlüssel sind an individuelle Benutzerkonten gebunden
  • Hochgeladene Dateien erstellen Spaces, die dem Inhaber des API-Schlüssels gehören
  • Vorsignierte URLs laufen nach begrenzter Zeit ab — laden Sie zeitnah hoch
  • Bewahren Sie Ihren API-Schlüssel sicher auf und stellen Sie ihn niemals in clientseitigem Code bereit