> ## Documentation Index
> Fetch the complete documentation index at: https://www.octoparse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Workspace speichern

> Mutablen Draft (Workspace) der App speichern. Ungültiger Inhalt speicherbar; Validierungsergebnis in der Response.

**`PUT`** `https://api-datahub.octoparse.com/v1/data-apps/{app_id}/workspace`

Authentifizierung: API-Schlüssel erforderlich (`Authorization: Bearer <API Key>`). Nur App-Autor; andere erhalten `404`.

Mutablen Draft speichern. **Saves nie wegen Validierungsfehlern abgelehnt**: volle Validierung läuft, Ergebnis (`valid` / `issues`) in der Response. „Halbfertigen Draft speichern und später zurück“ ist Modellgarantie.

Die App-Identität entsteht implizit beim ersten Save — es gibt keinen separaten „create app“-Endpoint. Nur drei Hard-Gates liefern `400`: das Konto hat einen Username, der Payload liegt in den Größenlimits, und angehängte Dateinamen sind gültig. `identity.app_name` im Manifest wird nicht mehr mit dem Pfad verglichen: der Routenname folgt der Referenz, und der in der Datei deklarierte Name wird nur bei der Erstellung verbraucht.

`expected_revision` bietet optimistische Concurrency (`409 revision-conflict`), damit Tabs sich nicht still überschreiben. Angehängte Secrets nur bei gültigem Save; nicht gespeicherte Namen in `secrets_deferred`. Deprecated Apps bleiben editierbar; nur neue Releases werden abgelehnt.

## Anfrage

### Pfadparameter

<ParamField path="app_id" type="string" required>
  App-Referenz: `app_<hex>` oder `<namespace>/<app_name>`. Use the latter on first save.
</ParamField>

### Anfrage-Body

<ParamField body="manifest" type="string" required>
  Roher Manifesttext.
</ParamField>

<ParamField body="files" type="object">
  Angehängte Dateien: Schlüssel sind relative Pfade, Werte sind Textinhalte.
</ParamField>

<ParamField body="secrets" type="object">
  Map von Secret-Namen zu Klartextwerten. Nur gespeichert, wenn dieser Save gültig ist.
</ParamField>

<ParamField body="expected_revision" type="integer">
  Expected current draft revision. Mismatch gibt zurück `409`.
</ParamField>

### Beispielanfrage

```bash theme={null}
curl -X PUT \
  -H "Authorization: Bearer $OCTOPARSE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"manifest": "spec_version: \"0.2\"\n…", "files": {}, "expected_revision": 3}' \
  "https://api-datahub.octoparse.com/v1/data-apps/carol/reviews-query/workspace"
```

## Antwort

### 200 Erfolg

```json theme={null}
{
  "data": {
    "app_name": "string",
    "app_id": "string",
    "namespace": "string",
    "revision": 0,
    "valid": false,
    "issues": [
      {
        "path": "string",
        "message": "string"
      }
    ],
    "missing_secrets": [
      "string"
    ],
    "secrets_deferred": [
      "string"
    ],
    "name_reused_from": "string"
  }
}
```

Payload ist in `data` gewrappt. Felder:

<ResponseField name="app_name" type="string" required>
  —
</ResponseField>

<ResponseField name="app_id" type="string" required>
  Stabile App-ID.
</ResponseField>

<ResponseField name="namespace" type="string">
  —
</ResponseField>

<ResponseField name="revision" type="integer" required>
  Draft-Revision nach dem Speichern.
</ResponseField>

<ResponseField name="valid" type="boolean" required>
  Ob der aktuelle Inhalt die Validierung bestanden hat.
</ResponseField>

<ResponseField name="issues" type="object[]">
  Problemliste.

  <Expandable title="fields">
    <ResponseField name="path" type="string" required>
      —
    </ResponseField>

    <ResponseField name="message" type="string" required>
      —
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="missing_secrets" type="string[]">
  —
</ResponseField>

<ResponseField name="secrets_deferred" type="string[]">
  Secret-Namen nicht gespeichert, weil dieser Save ungültig war.
</ResponseField>

<ResponseField name="name_reused_from" type="string">
  —
</ResponseField>

### Fehler

| HTTP | `code`              | `category`      | Beschreibung                                                                                                                          |
| ---- | ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`      | `forbidden`     | Fehlender oder ungültiger API-Schlüssel.                                                                                              |
| 400  | `username-required` | `invalid_input` | Account has no Username yet, so a `<Username>/<app_name>` reference kann nicht formed.                                                |
| 409  | `revision-conflict` | `invalid_input` | `expected_revision` stimmt nicht mit der aktuellen Draft-Revision überein – gleichzeitige Bearbeitung.                                |
| 400  | `invalid-app-name`  | `invalid_input` | Name verletzt Naming-Regeln oder ist ein reserviertes Wort.                                                                           |
| 409  | `app-name-reserved` | `forbidden`     | Der Name ist durch eine historische Bindung einer anderen Person belegt (30-tägige Sperre nach einer Übertragung des Benutzernamens). |
| 413  | `payload-too-large` | `invalid_input` | Request-Body überschreitet das Größenlimit.                                                                                           |

Fehlerantworten nutzen `{"error": {code, category, message, retryable}}`. Siehe <a href="/docs/de/datahub/api/reference/introduction#errors">Fehler</a>.

## Client-Bibliotheken

Die Python- und JavaScript-SDKs wrappen diesen Endpoint noch nicht. REST direkt aufrufen.
