> ## 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.

# Salva workspace

> Salva la bozza mutabile (workspace) dell’app. Contenuto non valido comunque salvabile; risultati validazione in response.

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

Autenticazione: API key obbligatoria (`Authorization: Bearer <API Key>`). Solo autore dell’app; gli altri ricevono `404`.

Salva la bozza mutabile. **I save non sono mai rifiutati per fallimento di validazione**: la validazione completa gira e il risultato (`valid` / `issues`) torna nella response. “Salva una bozza a metà e torna dopo” è garanzia del modello.

L’identità app nasce implicitamente al primo save — non c’è un endpoint separato “create app”. Solo tre hard gate restituiscono `400`: l’account ha uno username, il payload è entro i limiti di size e i nomi file allegati sono validi. `identity.app_name` nel manifest non è più confrontato col path: il nome di route segue il riferimento e il nome dichiarato nel file è consumato solo in creazione.

`expected_revision` offre concurrency ottimistica (`409 revision-conflict`) così più tab non si sovrascrivono in silenzio. I secret allegati solo se il save è valido; i nomi non memorizzati in `secrets_deferred`. Le app deprecated restano editabili; solo i nuovi Release sono rifiutati.

## Richiesta

### Parametri di percorso

<ParamField path="app_id" type="string" required>
  Riferimento app: `app_<hex>` oppure `<namespace>/<app_name>`. Use the latter on first save.
</ParamField>

### Body della richiesta

<ParamField body="manifest" type="string" required>
  Testo grezzo del manifest.
</ParamField>

<ParamField body="files" type="object">
  File allegati: le chiavi sono percorsi relativi, i valori sono contenuti testuali.
</ParamField>

<ParamField body="secrets" type="object">
  Mappa da nomi secret a valori in chiaro. Memorizzata solo se questo save è valido.
</ParamField>

<ParamField body="expected_revision" type="integer">
  Expected current draft revision. Mismatch restituisce `409`.
</ParamField>

### Esempio di richiesta

```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"
```

## Risposta

### 200 successo

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

Il payload è wrappato in `data`. Campi:

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

<ResponseField name="app_id" type="string" required>
  ID app stabile.
</ResponseField>

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

<ResponseField name="revision" type="integer" required>
  Revisione del draft dopo il salvataggio.
</ResponseField>

<ResponseField name="valid" type="boolean" required>
  Se il contenuto corrente ha superato la validazione.
</ResponseField>

<ResponseField name="issues" type="object[]">
  Elenco dei problemi.

  <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[]">
  Nomi secret non memorizzati perché questo save non era valido.
</ResponseField>

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

### Errori

| HTTP | `code`              | `category`      | Descrizione                                                                                                               |
| ---- | ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 401  | `unauthorized`      | `forbidden`     | API key mancante o non valida.                                                                                            |
| 400  | `username-required` | `invalid_input` | Account has no username yet, so a `<username>/<app_name>` reference non può essere formed.                                |
| 409  | `revision-conflict` | `invalid_input` | `expected_revision` non corrisponde alla revisione attuale del draft: modifica concorrente.                               |
| 400  | `invalid-app-name`  | `invalid_input` | Il nome viola le regole di naming o è una parola riservata.                                                               |
| 409  | `app-name-reserved` | `forbidden`     | Il nome è occupato da un binding storico di un'altra persona (blocco di 30 giorni dopo un trasferimento del nome utente). |
| 413  | `payload-too-large` | `invalid_input` | Il body supera il limite di size.                                                                                         |

Le risposte di errore usano `{"error": {code, category, message, retryable}}`. Vedi <a href="/docs/it/datahub/api/reference/introduction#errors">Errori</a>.

## Librerie client

Gli SDK Python e JavaScript non wrappano ancora questo endpoint. Chiama REST direttamente.
