# `POST /v1/workspace/apps/{appId}/save` — Save

Part of the [Bkper Managed Agent API](https://bkper.com/docs/api/managed-agent.md). Read its guide for authentication, conventions, and examples.

Publish the working copy exactly as reviewed (`fingerprint`): the agent's commits not yet published as they are, plus one commit of what is not committed, authored and committed by the user with an automatic message. When someone published meanwhile, Save builds on what they published. 409 review_stale when the working copy changed since the review: review it again. 409 publish_conflict when they changed the same lines: nothing is published and the working copy is as before, `paths` naming the files and `publishers` who published them. 409 app_unregistered for an App Bkper does not know yet: the agent registers it. 403 workspace_forbidden when the user may not publish it; 422 file_invalid (with reason) in context, for what agents would not load; 409 workspace_busy while Git works in the repository. For the App's owner and developers (403 workspace_forbidden), when Bkper hosts its source: its folder in /workspace/apps. Saving never deploys.

## Operation contract

```json
{
  "operationId": "saveAppSource",
  "tags": [
    "Workspace"
  ],
  "summary": "Save",
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "parameters": [
    {
      "name": "appId",
      "in": "path",
      "required": true,
      "schema": {
        "type": "string",
        "pattern": "^[A-Za-z0-9_-]{1,64}$"
      }
    }
  ],
  "description": "Publish the working copy exactly as reviewed (`fingerprint`): the agent's commits not yet published as they are, plus one commit of what is not committed, authored and committed by the user with an automatic message. When someone published meanwhile, Save builds on what they published. 409 review_stale when the working copy changed since the review: review it again. 409 publish_conflict when they changed the same lines: nothing is published and the working copy is as before, `paths` naming the files and `publishers` who published them. 409 app_unregistered for an App Bkper does not know yet: the agent registers it. 403 workspace_forbidden when the user may not publish it; 422 file_invalid (with reason) in context, for what agents would not load; 409 workspace_busy while Git works in the repository. For the App's owner and developers (403 workspace_forbidden), when Bkper hosts its source: its folder in /workspace/apps. Saving never deploys.",
  "requestBody": {
    "required": true,
    "content": {
      "application/json": {
        "schema": {
          "$ref": "#/components/schemas/SaveWorkspace"
        }
      }
    }
  },
  "responses": {
    "200": {
      "description": "Success",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/WorkspaceCommit"
          }
        }
      }
    },
    "400": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "401": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "403": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "404": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "409": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "410": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "411": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "413": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "422": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "503": {
      "description": "Rejected; never contains credentials or upstream payloads.",
      "content": {
        "application/json": {
          "schema": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    }
  }
}
```

## Schemas

### SaveWorkspace

```json
{
  "type": "object",
  "properties": {
    "fingerprint": {
      "type": "string",
      "pattern": "^[0-9a-f]{40,64}$",
      "description": "The `fingerprint` of the review the user saw. If the working copy changed since, 409 review_stale: review it again."
    }
  },
  "required": [
    "fingerprint"
  ],
  "additionalProperties": false
}
```

### WorkspaceCommit

```json
{
  "type": "object",
  "properties": {
    "commit": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "description": "The latest published commit; the same one when there was nothing to publish."
    },
    "paths": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "The files published, compared with the base."
    }
  },
  "required": [
    "commit",
    "paths"
  ],
  "description": "What Save published."
}
```

### Error

```json
{
  "type": "object",
  "properties": {
    "error": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string",
          "description": "Stable code, e.g. not_found, idempotency_conflict, session_working, usage_limit_exceeded. Open."
        },
        "type": {
          "description": "Category, e.g. invalid_request_error, authentication_error, usage_limit_error. Open.",
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "host": {
          "description": "host_in_use: the host another service holds.",
          "type": "string"
        },
        "service": {
          "description": "host_in_use: the id of the service that holds the host.",
          "type": "string"
        },
        "reason": {
          "description": "file_invalid: why the change, or a picture, was refused. Open: path | text | size | skill | skills | count | type.",
          "type": "string"
        },
        "paths": {
          "description": "file_exists from an add: every path of the request where a file is and `replace` was not set, all at once, so the person can be asked once. publish_conflict: the files someone published changes to the same lines of.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "publishers": {
          "description": "publish_conflict: who published those changes, by name.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "code"
      ]
    }
  },
  "required": [
    "error"
  ]
}
```

## Authentication

```json
{
  "bearerAuth": {
    "type": "http",
    "scheme": "bearer"
  }
}
```
