

```json
{
  "openapi": "3.0.0",
  "info": {
    "title": "Cloud Save Files",
    "version": "1.0.0",
    "description": "# Introduction\nThis is the API specification for the Unity Cloud Save service that allows player or custom non-player state to be accessed and persisted from a game client.\nThe Player State API allows data to be stored against a specific player ID. Access using client authentication is limited to the specified player for both data reads and writes.\nThe Game State API allows data to be stored against a custom ID provided by the user. All players are able to read the data from the custom ID using client authentication, but write operations are limited to Service Account authentication.\nTo use this API, you must first enable it through the Unity Gaming Services dashboard.\n\n# General Information\n**Cloud Save Files** is for storing data files (binary, JSON, text, CSV, etc.) against keys. The maximum size for player data is 1 GB across all keys for that player. A player can have data in a single file that is up to 1 GB in size, or use many files that equal to less than 1 GB in total size.\nLimits:\n  - Maximum 255 characters per file name\n  - Maximum 200 files per player\n  - Maximum 1 GB in size per player across all files\n\n## Rate Limits\nThe API has rate limiting in place. The endpoints are limited to 600 requests per minute on a per-player basis.\nThe API responds with a `429` HTTP status code if the rate limit is exceeded.\nIt will also respond with a `Retry-After` header to be used in conjunction with a client's retry logic. The value is the number of seconds until a request for the given player will be accepted.\n",
    "contact": {
      "name": "Cloud Save Team",
      "url": "https://discussions.unity.com/tag/cloud-save"
    }
  },
  "servers": [
    {
      "url": "https://cloud-save.services.api.unity.com/v1/files"
    }
  ],
  "tags": [
    {
      "name": "Files",
      "description": "Associated with the Cloud Save Files service."
    }
  ],
  "security": [
    {
      "Client": []
    },
    {
      "ServiceAccount": []
    }
  ],
  "x-anchors": {
    "readBillingEvents": {
      "events": [
        {
          "service": "cloud-save-files",
          "sku": "UGS",
          "uom": "Cloud Save - Reads",
          "qtySource": "number",
          "qty": 1,
          "parameters": [
            {
              "source": "path",
              "resourceType": "project",
              "resourceId": "projectId"
            }
          ]
        }
      ]
    },
    "writeBillingEvents": {
      "events": [
        {
          "service": "cloud-save-files",
          "sku": "UGS",
          "uom": "Cloud Save - Writes",
          "qtySource": "number",
          "qty": 1,
          "parameters": [
            {
              "source": "path",
              "resourceType": "project",
              "resourceId": "projectId"
            }
          ]
        }
      ]
    }
  },
  "paths": {
    "/projects/{projectId}/players/{playerId}/items": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProjectId"
        },
        {
          "$ref": "#/components/parameters/PlayerId"
        }
      ],
      "get": {
        "summary": "List Player Files",
        "operationId": "listPlayerFiles",
        "tags": [
          "Files"
        ],
        "description": "Retrieves a list of names of all the files currently saved in storage for a player along with size (in bytes), last date modified and current WriteLock.\nOrdered alphabetically in page sizes of 20.\n",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "in": "query",
            "name": "after",
            "description": "The key after which to retrieve the next page of files."
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{projectId}/players/{playerId}/items/{key}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProjectId"
        },
        {
          "$ref": "#/components/parameters/PlayerId"
        },
        {
          "$ref": "#/components/parameters/Key"
        }
      ],
      "get": {
        "operationId": "getDownloadUrl",
        "summary": "Get Player File Download URL",
        "tags": [
          "Files"
        ],
        "description": "Generates a signed URL that will allow the client to download the requested player file for a limited period of time.\nIf successful, an object will be returned containing all necessary information to perform the download.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedUrlResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "post": {
        "operationId": "getUploadUrl",
        "summary": "Get Player File upload URL",
        "tags": [
          "Files"
        ],
        "description": "Initiates the player file upload process and returns a URL to which the actual file contents can be uploaded.\nThe type, length and MD5 hash of the file have to be provided up-front.\nIf successful, an object will be returned containing all necessary information to perform the upload.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FileDetails"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignedUrlResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      },
      "delete": {
        "summary": "Delete Player File",
        "operationId": "deleteFile",
        "tags": [
          "Files"
        ],
        "description": "Deletes the specified player file. If a WriteLock is provided, the file will only be deleted if it matches the stored WriteLock.",
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/WriteLock"
            },
            "in": "query",
            "name": "writeLock",
            "description": "Enforces conflict checking when deleting an existing file.\nOmitting this field ignores write conflicts. When present, an error response will be returned if the writeLock in the request does not match the stored writeLock."
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/projects/{projectId}/players/{playerId}/items/{key}/metadata": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ProjectId"
        },
        {
          "$ref": "#/components/parameters/PlayerId"
        },
        {
          "$ref": "#/components/parameters/Key"
        }
      ],
      "get": {
        "summary": "Get Player File Metadata",
        "operationId": "getFileMetadata",
        "tags": [
          "Files"
        ],
        "description": "Retrieves the metadata (size, date last modified and created, key, content type, and current WriteLock) of a single player file.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "Client": {
        "type": "http",
        "scheme": "bearer",
        "description": "To get started with Authentication, please visit the\n[Client Authentication section](https://services.docs.unity.com/docs/client-auth)."
      },
      "ServiceAccount": {
        "type": "http",
        "scheme": "bearer",
        "description": "To get started with Authentication, please visit the\n[Service Account Authentication section](https://services.docs.unity.com/docs/service-account-auth).\nWhen using a Service Account, these APIs require exchanging Service Account credentials for\nan access token, please see the section about the\n[Token Exchange API](https://services.docs.unity.com/docs/service-account-auth/index.html#extra-step-use-the-token-exchange-api)."
      }
    },
    "parameters": {
      "ProjectId": {
        "name": "projectId",
        "in": "path",
        "description": "ID of the project.",
        "required": true,
        "schema": {
          "type": "string"
        },
        "example": "8bdacc33-6eef-4577-beb0-633c86259f5b"
      },
      "PlayerId": {
        "name": "playerId",
        "in": "path",
        "description": "The player ID supplied by the Authorization service.",
        "required": true,
        "schema": {
          "type": "string"
        },
        "example": "s1QFdyYFXCC2YBocplKoLstxvQ2r"
      },
      "Key": {
        "name": "key",
        "in": "path",
        "description": "Item key.",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/Key"
        }
      }
    },
    "schemas": {
      "Key": {
        "title": "Key",
        "type": "string",
        "pattern": "^[A-Za-z0-9_-][A-Za-z0-9_.-]{0,254}$",
        "description": "File key. Alphanumeric characters, underscores, dashes and periods; must not start with a period.",
        "minLength": 1,
        "maxLength": 255,
        "example": "save-slot-1.dat"
      },
      "WriteLock": {
        "title": "WriteLock",
        "type": "string",
        "description": "Enforces conflict checking when updating an existing file. This field should be omitted when creating a new file.\nWhen updating an existing file, omitting this field ignores write conflicts. When present, an error response will be returned if the writeLock in the request does not match the stored writeLock.\nThe writeLock is an opaque value: send back the value returned by this API without changing or interpreting it.",
        "example": "12345678901234567890"
      },
      "ModifiedMetadata": {
        "title": "ModifiedMetadata",
        "type": "object",
        "description": "Timestamp for when the object was modified.",
        "properties": {
          "date": {
            "$ref": "#/components/schemas/IsoDate"
          }
        },
        "required": [
          "date"
        ]
      },
      "IsoDate": {
        "type": "string",
        "nullable": true,
        "format": "date-time",
        "description": "Date time in ISO 8601 format. Null if there is no associated value.",
        "example": "2020-04-01T13:07:23Z"
      },
      "FileItem": {
        "title": "FileItem",
        "type": "object",
        "description": "Response type for a file stored in the Cloud Save service.",
        "properties": {
          "key": {
            "$ref": "#/components/schemas/Key"
          },
          "writeLock": {
            "$ref": "#/components/schemas/WriteLock"
          },
          "modified": {
            "$ref": "#/components/schemas/ModifiedMetadata"
          },
          "created": {
            "$ref": "#/components/schemas/ModifiedMetadata"
          },
          "contentType": {
            "type": "string",
            "description": "The MIME type of the stored file",
            "example": "text/plain"
          },
          "size": {
            "type": "integer",
            "format": "int64",
            "description": "The size of the stored file in bytes",
            "example": 2048
          }
        },
        "required": [
          "key",
          "writeLock",
          "modified",
          "created",
          "contentType",
          "size"
        ]
      },
      "FileList": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FileItem"
            },
            "description": "The array of returned file items"
          },
          "links": {
            "type": "object",
            "properties": {
              "next": {
                "type": "string",
                "nullable": true
              }
            },
            "description": "Links object containing the link for the next page of results"
          }
        }
      },
      "FileDetails": {
        "title": "FileDetails",
        "type": "object",
        "description": "Details included when generating an upload URL for a file",
        "properties": {
          "contentType": {
            "type": "string",
            "description": "The MIME type of the file that will be uploaded"
          },
          "contentLength": {
            "type": "integer",
            "format": "int64",
            "description": "The content length in bytes of the file that will be uploaded"
          },
          "contentMd5": {
            "type": "string",
            "description": "The base64 encoded MD5 checksum of the file contents that will be uploaded"
          },
          "writeLock": {
            "$ref": "#/components/schemas/WriteLock"
          }
        },
        "required": [
          "contentType",
          "contentLength",
          "contentMd5"
        ]
      },
      "SignedUrlResponse": {
        "type": "object",
        "properties": {
          "signedUrl": {
            "type": "string",
            "description": "The signed URL used to access the resource."
          },
          "httpMethod": {
            "type": "string",
            "description": "The HTTP method that must be used on the signedUrl."
          },
          "requiredHeaders": {
            "type": "object",
            "description": "The set of HTTP headers that must be sent with the request for it to succeed."
          }
        },
        "required": [
          "signedUrl",
          "httpMethod",
          "requiredHeaders"
        ]
      },
      "BaseErrorResponse": {
        "properties": {
          "type": {
            "type": "string",
            "description": "A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when dereferenced, it provide human-readable documentation for the problem type (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be \"about:blank\"."
          },
          "title": {
            "type": "string",
            "description": "A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence of the problem, except for purposes of localization (e.g., using proactive content negotiation; see [RFC7231], Section 3.4)."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code ([RFC7231], Section 6) generated by the origin server for this occurrence of the problem."
          },
          "code": {
            "description": "Service specific error code",
            "type": "integer"
          },
          "detail": {
            "type": "string",
            "description": "A human-readable explanation specific to this occurrence of the problem."
          },
          "instance": {
            "type": "string",
            "nullable": true,
            "description": "A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced."
          }
        },
        "required": [
          "type"
        ]
      },
      "BasicErrorResponse": {
        "title": "BasicErrorResponse",
        "description": "Referenced from - https://tools.ietf.org/html/rfc7807#page-3\nConsumers MUST use the \"type\" string as the primary identifier for the problem type; the 'title' string is advisory and included only for users who are not aware of the semantics of the URI and do not have the ability to discover them (e.g., offline log analysis). Consumers SHOULD NOT automatically dereference the type URI.\nThe \"status\" member, if present, is only advisory; it conveys the HTTP status code used for the convenience of the consumer. Generators MUST use the same status code in the actual HTTP response, to assure that generic HTTP software that does not understand this format still behaves correctly.  See Section 5 for further caveats regarding its use.\nConsumers can use the status member to determine what the original status code used by the generator was, in cases where it has been changed (e.g., by an intermediary or cache), and when message bodies persist without HTTP information.  Generic HTTP software will still use the HTTP status code.\nThe \"detail\" member, if present, ought to focus on helping the client correct the problem, rather than giving debugging information.\n",
        "type": "object",
        "example": {
          "type": "problems/basic",
          "title": "service unavailable",
          "status": 500,
          "code": 1000,
          "detail": "service unavailable",
          "details": [
            {
              "health": [
                "service health ping failed"
              ]
            }
          ]
        },
        "allOf": [
          {
            "$ref": "#/components/schemas/BaseErrorResponse"
          },
          {
            "properties": {
              "details": {
                "type": "array",
                "description": "Machine readable service specific errors.",
                "items": {
                  "type": "object"
                }
              }
            }
          }
        ]
      },
      "ValidationErrorResponse": {
        "title": "ValidationErrorResponse",
        "type": "object",
        "description": "Validation error response when a value provided from the client does not pass validation on server.",
        "example": {
          "type": "problems/validation",
          "title": "validation error",
          "status": 400,
          "detail": "See 'errors' for specific validation errors",
          "code": 1009,
          "errors": [
            {
              "field": "name",
              "messages": [
                "Only alphanumeric characters allowed"
              ]
            }
          ]
        },
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "code": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ValidationErrorBody"
            }
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code",
          "detail",
          "errors"
        ]
      },
      "ValidationErrorBody": {
        "title": "ValidationErrorBody",
        "type": "object",
        "properties": {
          "field": {
            "type": "string"
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "field",
          "messages"
        ],
        "description": "Single error in the Validation Error Response."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad Request. Returned code indicates one of:\n- Request parameter validation failure\n- Other\nSee the errors field for more details.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/BasicErrorResponse"
                },
                {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              ],
              "discriminator": {
                "propertyName": "type",
                "mapping": {
                  "problems/basic": "#/components/schemas/BasicErrorResponse",
                  "problems/validation": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Invalid Authentication Token",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not Found",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict. The 'writeLock' supplied in the request does not match the current stored state of this file; another client has changed the file since you acquired this writeLock.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too Many Requests",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "number"
            },
            "description": "The number of seconds until a request will be accepted"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Internal Server Error",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Service Unavailable",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/BasicErrorResponse"
            }
          }
        }
      }
    }
  }
}
```