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

# Guardar workspace

> Guarda el borrador mutable de la app (workspace). El contenido inválido se puede guardar; la validación vuelve en la respuesta.

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

Autenticación: se requiere API key (`Authorization: Bearer <API Key>`). Solo el autor de la app; el resto recibe `404`.

Guarda el borrador mutable de la app. **Los guardados nunca se rechazan por fallo de validación**: la validación completa sigue ejecutándose y el resultado (`valid` / `issues`) vuelve en la respuesta. «Guardar un borrador a medias y volver después» es una garantía del modelo.

La identidad de la app se crea de forma implícita en el primer guardado — no hay un endpoint aparte de «crear app». Use `<namespace>/<app_name>` en el primer save; después prefiera el `app_id` estable.

`expected_revision` aporta concurrencia optimista (`409 revision-conflict`) para que varios editores no se pisen. Omítalo solo en herramientas de un solo escritor; los clientes interactivos deben enviarlo siempre.

## Solicitud

### Parámetros de ruta

<ParamField path="app_id" type="string" required>
  Referencia de app: `app_<hex>` o `<namespace>/<app_name>`. Use la segunda en el primer guardado.
</ParamField>

### Cuerpo de la solicitud

<ParamField body="manifest" type="string" required>
  Texto crudo del manifest.
</ParamField>

<ParamField body="files" type="object">
  Archivos adjuntos: claves = rutas relativas, valores = contenido de texto.
</ParamField>

<ParamField body="secrets" type="object">
  Mapa de nombres de secreto a valores en texto plano. Solo se guarda si este save es válido.
</ParamField>

<ParamField body="expected_revision" type="integer">
  Revisión actual esperada del borrador. Si no coincide, `409`.
</ParamField>

### Ejemplo de solicitud

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

## Respuesta

### 200 correcto

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

La carga útil va envuelta en `data`. Campos:

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

<ResponseField name="app_id" type="string" required>
  Id estable de la app.
</ResponseField>

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

<ResponseField name="revision" type="integer" required>
  Revisión del borrador tras guardar.
</ResponseField>

<ResponseField name="valid" type="boolean" required>
  Si el contenido actual pasó la validación.
</ResponseField>

<ResponseField name="issues" type="object[]">
  Lista de incidencias.

  <Expandable title="campos">
    <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[]">
  Nombres de secreto no guardados porque este guardado no fue válido.
</ResponseField>

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

### Errores

| HTTP | `code`              | `category`      | Descripción                                                                                                        |
| ---- | ------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------ |
| 401  | `unauthorized`      | `forbidden`     | API key ausente o no válida.                                                                                       |
| 400  | `username-required` | `invalid_input` | La cuenta aún no tiene username, así que no se puede formar una referencia `<username>/<app_name>`.                |
| 409  | `revision-conflict` | `invalid_input` | `expected_revision` no coincide con la revisión actual del borrador — edición concurrente.                         |
| 400  | `invalid-app-name`  | `invalid_input` | El nombre no cumple las reglas de nomenclatura o es una palabra reservada.                                         |
| 409  | `app-name-reserved` | `forbidden`     | El nombre lo retiene un binding histórico de otra persona (congelación de 30 días tras transferencia de username). |
| 413  | `payload-too-large` | `invalid_input` | El cuerpo supera el límite de tamaño.                                                                              |

Las respuestas de error usan `{"error": {code, category, message, retryable}}`. Véase <a href="/docs/es/datahub/api/reference/introduction#errors">Errores</a>.

## Bibliotecas cliente

Los SDK de Python y JavaScript aún no encapsulan este endpoint. Llame a REST directamente.
