{"openapi":"3.1.2","info":{"title":"SendPulse FileManager Public API","description":"Using the API for the FileManager service, you can integrate your system with FileManager from SendPulse","version":"1.0.0"},"servers":[{"url":"https:\/\/api.sendpulse.com\/fm\/public\/v1","description":"Production server"}],"tags":[{"name":"File","description":"Upload, delete, check and filter files in the user storage"},{"name":"Directory","description":"Create directories, browse the directory tree and contents, search and get storage statistics"}],"paths":{"\/file":{"post":{"tags":["File"],"operationId":"uploadFile","summary":"Upload file to storage","description":"Uploads one or multiple files to the specified directory.\nA file with the same name and a different size is stored with a `_<crc32 of size>` suffix.\nFile size and free disk space are limited by the user's tariff.\n","requestBody":{"required":true,"content":{"multipart\/form-data":{"schema":{"type":"object","required":["content[]","pathToStore"],"properties":{"content[]":{"type":"array","minItems":1,"items":{"type":"string","format":"binary"},"description":"File(s) to upload. File name: up to 255 characters,\nmust not contain any of: & ? = + $ # @ % ^ ` ~ ; : < > { } [ ] | \\ \". Restricted file types are rejected.\n"},"pathToStore":{"type":"string","maxLength":1024,"example":"\/test","description":"Existing directory to store the file(s) in. `\/` is the root.\nLeading and trailing slashes are optional. Each path segment: up to 255 characters.\n"},"height":{"type":"integer","description":"Video height in pixels. Together with `width`, marks the video as vertical when height > width","example":1080},"width":{"type":"integer","description":"Video width in pixels. See `height`","example":1920}}}}}},"responses":{"200":{"description":"Files uploaded; one item per uploaded file, in request order","content":{"application\/json":{"schema":{"type":"array","items":{"$ref":"#\/components\/schemas\/UploadedFile"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}},"delete":{"tags":["File"],"operationId":"deleteFile","summary":"Delete file from storage","description":"Deletes a file together with its thumbnail and resized copies. A missing file is reported as `422`","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","description":"Path to an existing file. Leading and trailing slashes are optional","example":"\/directory\/file_name.txt"}}}}}},"responses":{"204":{"description":"File deleted"},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}}},"\/file\/exist":{"post":{"tags":["File"],"operationId":"checkFileExists","summary":"Check if file exists","description":"Checks that a file exists at the specified path.\nAn existing file returns `200` with `{\"result\": true}`; a missing file returns `422` (`File not exists`), never `\"result\": false`.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","example":"\/test\/file.txt","description":"Path to the file to check. Leading and trailing slashes are optional"}}}}}},"responses":{"200":{"description":"The file exists","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/FileExistsResult"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}}},"\/directory":{"post":{"tags":["Directory"],"operationId":"createDirectory","summary":"Create directory","description":"Creates a new directory inside an existing directory","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["pathToStore","name"],"properties":{"pathToStore":{"type":"string","maxLength":1024,"example":"\/test","description":"Existing parent directory. `\/` is the root. Leading and trailing slashes are optional. Each path segment is up to 255 characters"},"name":{"type":"string","maxLength":255,"example":"my-folder","description":"Name of the new directory. A directory with this name must not already exist in `pathToStore`.\nMust contain at least one letter, digit, `-`, `_`, `.` or space, and must not contain any of: & ? = + $ # @ % ^ ` ~ ; : < > { } [ ] | \\ \"\n"}}}}}},"responses":{"201":{"description":"Directory created","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/CreatedDirectory"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}},"get":{"tags":["Directory"],"operationId":"getDirectoryTree","summary":"Get directory tree","description":"Returns the full tree of the user's directories (files are not included).\nThe response is an array with a single root node (`path` = `\/`), nested directories are in `children`, sorted naturally and case-insensitively.\n","responses":{"200":{"description":"Directory tree","content":{"application\/json":{"schema":{"type":"array","minItems":1,"maxItems":1,"items":{"$ref":"#\/components\/schemas\/DirectoryTreeNode"}},"example":[{"path":"\/","name":"","children":[{"path":"\/test\/","name":"test","hasFiles":true,"children":[{"path":"\/test\/subfolder\/","name":"subfolder","hasFiles":false,"children":[]}]}]}]}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/directory\/size":{"get":{"tags":["Directory"],"operationId":"getStorageStats","summary":"Get storage statistics","description":"Returns storage usage of the user. All sizes are in bytes.\nCounters and `usedSpace` include service objects (thumbnails, resized copies), so they can be greater than what directory listings show.\n","responses":{"200":{"description":"Storage statistics","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/StorageStats"}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"}}}},"\/directory\/find":{"get":{"tags":["Directory"],"operationId":"searchFiles","summary":"Search files and directories","description":"Recursively searches the specified directory for directories and files whose name contains `search` (case-insensitive).\nDirectories come first, then files. Thumbnails and resized copies are not included.\n","parameters":[{"in":"query","name":"search","schema":{"type":"string"},"required":true,"description":"Substring to search for in names (case-insensitive)","example":"document"},{"in":"query","name":"path","schema":{"type":"string"},"required":true,"description":"Existing directory to search in. `\/` is the root","example":"\/test"}],"responses":{"200":{"description":"Matching directories and files; empty array if none","content":{"application\/json":{"schema":{"type":"array","items":{"oneOf":[{"$ref":"#\/components\/schemas\/SearchFolder"},{"$ref":"#\/components\/schemas\/SearchFile"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}}},"\/directory\/{path}":{"get":{"tags":["Directory"],"operationId":"getDirectoryContents","summary":"Get directory contents","description":"Returns the direct contents of a directory (not recursive): subdirectories first, then files, each group sorted naturally by path.\nTo list the root directory, pass `path:` as the path (`GET \/directory\/path:`). `GET \/directory\/` without a path returns the tree (see `getDirectoryTree`).\n","parameters":[{"in":"path","name":"path","schema":{"type":"string"},"required":true,"description":"Path to an existing directory. Nested directories are separated by `\/` (may be URL-encoded as `%2F`), e.g. `test\/subfolder`.\nThe path may be prefixed with `path:` (e.g. `path:test\/subfolder`); the prefix is stripped.\n`path:` alone means the root directory.\n","examples":{"nested":{"summary":"Nested directory","value":"test\/subfolder"},"withPrefix":{"summary":"Nested directory with the `path:` prefix","value":"path:test\/subfolder"},"root":{"summary":"Root directory","value":"path:"}}}],"responses":{"200":{"description":"Directory entries; empty array if the directory is empty","content":{"application\/json":{"schema":{"type":"array","items":{"oneOf":[{"$ref":"#\/components\/schemas\/DirectoryEntryFolder"},{"$ref":"#\/components\/schemas\/DirectoryEntryFile"}]}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}}},"\/file\/filter":{"post":{"tags":["File"],"operationId":"filterFiles","summary":"Filter files","description":"Returns all files in the specified directory and its subdirectories (recursively), sorted by modification date\nand optionally limited to a date range. Directories are not included.\nFor `path` = `\/` the result also includes thumbnails stored under `\/thumb\/`.\n","requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["path"],"properties":{"path":{"type":"string","example":"\/test","description":"Existing directory to search in. `\/` is the root. Leading and trailing slashes are optional"},"fromDate":{"type":["string","null"],"example":"2025-01-01 00:01","description":"Include files modified at or after this moment (`>=`). Any date\/time string parsable by PHP `strtotime()`, e.g. `2025-01-01`, `2025-01-01 00:01` or ISO 8601. Timezone: UTC unless given in the value"},"toDate":{"type":["string","null"],"example":"2025-12-31 23:59","description":"Include files modified at or before this moment (`<=`). Same format as `fromDate`. A date without time means `00:00:00` of that day"},"sort":{"type":"string","enum":["asc","desc"],"default":"asc","example":"desc","description":"Sort order by modification date. `null` is accepted and means `asc`"}}}}}},"responses":{"200":{"description":"Matching files; empty array if none","content":{"application\/json":{"schema":{"type":"array","items":{"$ref":"#\/components\/schemas\/FilteredFile"}}}}},"401":{"$ref":"#\/components\/responses\/Unauthorized"},"403":{"$ref":"#\/components\/responses\/Forbidden"},"422":{"$ref":"#\/components\/responses\/ValidationFailed"}}}}},"components":{"schemas":{"UploadedFile":{"type":"object","required":["path","name","stream","extension","isFolder","thumb","size","date"],"properties":{"path":{"type":"string","description":"Full path to the stored file","example":"\/test\/photo.jpg"},"name":{"type":"string","description":"Stored file name (may have a `_<crc32>` suffix, see the upload description)","example":"photo.jpg"},"stream":{"type":"string","description":"Path for video streaming. Cyrillic is transliterated to Latin, other characters except `a-zA-Z0-9\/.-_()` are replaced with `_`","example":"\/test\/photo.jpg"},"extension":{"type":["string","null"],"description":"MIME type detected from the file name","example":"image\/jpeg"},"isFolder":{"type":"boolean","example":false},"thumb":{"type":["string","null"],"description":"Thumbnail path for images, `null` for other files. For images up to 122x91 px no thumbnail is generated, but the path is still returned","example":"\/thumb\/test\/photo.jpg"},"size":{"type":"integer","description":"File size in bytes","example":204800},"date":{"type":"integer","description":"Upload time, Unix timestamp in seconds","example":1759406400}}},"FileExistsResult":{"type":"object","required":["result"],"properties":{"result":{"type":"boolean","description":"Always `true`; a missing file is reported as `422`","example":true}}},"FilteredFile":{"type":"object","required":["path","lastModified"],"properties":{"path":{"type":"string","example":"\/test\/test_1.txt","description":"Full path to the file"},"lastModified":{"type":"string","format":"date-time","example":"2025-10-17T14:12:09Z","description":"Last modification time, ISO 8601 in UTC"}}},"CreatedDirectory":{"type":"object","required":["name","path","isFolder","size","filesCount","children"],"properties":{"name":{"type":"string","example":"my-folder"},"path":{"type":"string","description":"Full path to the new directory, with leading and trailing slashes","example":"\/test\/my-folder\/"},"isFolder":{"type":"boolean","example":true},"size":{"type":"integer","description":"Always `0` for a new directory","example":0},"filesCount":{"type":"integer","description":"Always `0` for a new directory","example":0},"children":{"$ref":"#\/components\/schemas\/EmptyArray"}}},"DirectoryTreeNode":{"type":"object","required":["path","name","children"],"properties":{"path":{"type":"string","description":"Full path to the directory, with leading and trailing slashes; `\/` for the root","example":"\/test\/"},"name":{"type":"string","description":"Directory name; empty string for the root","example":"test"},"hasFiles":{"type":"boolean","description":"Whether the directory directly contains files. Absent on the root node","example":true},"children":{"type":"array","items":{"$ref":"#\/components\/schemas\/DirectoryTreeNode"}}}},"StorageStats":{"type":"object","required":["fileCount","directoryCount","usedSpace","tariffStorageSpace","availableSpace"],"properties":{"fileCount":{"type":"integer","description":"Number of files, including service objects","example":150},"directoryCount":{"type":"integer","description":"Number of directories, including service directories","example":25},"usedSpace":{"type":"integer","format":"int64","description":"Used space in bytes","example":257634713},"tariffStorageSpace":{"type":"integer","format":"int64","description":"Storage space allowed by the tariff, in bytes","example":1073741824},"availableSpace":{"type":"integer","format":"int64","description":"`tariffStorageSpace` - `usedSpace` in bytes; negative when the quota is exceeded","example":816107111}}},"SearchFolder":{"type":"object","required":["path","name","isFolder","size","filesCount"],"properties":{"path":{"type":"string","description":"Full path to the directory, with a leading slash and without a trailing one","example":"\/test\/documents"},"name":{"type":"string","example":"documents"},"isFolder":{"type":"boolean","enum":[true],"example":true},"size":{"type":"integer","format":"int64","description":"Total size of files in the directory (recursively), in bytes","example":2621440},"filesCount":{"type":"integer","description":"Number of files in the directory (recursively)","example":3}}},"SearchFile":{"type":"object","required":["path","name","isFolder","size","date","extension","thumb"],"properties":{"path":{"type":"string","example":"\/test\/document.pdf"},"name":{"type":"string","example":"document.pdf"},"isFolder":{"type":"boolean","enum":[false],"example":false},"size":{"type":"integer","format":"int64","description":"File size in bytes","example":2621440},"date":{"type":"integer","description":"Last modification time, Unix timestamp in seconds","example":1760529600},"extension":{"type":["string","null"],"description":"MIME type detected from the file name","example":"application\/pdf"},"thumb":{"type":["string","null"],"description":"Thumbnail path; `null` if there is no thumbnail","example":"\/thumb\/test\/document.jpg"}}},"DirectoryEntryFolder":{"type":"object","required":["path","name","isFolder","size","filesCount","directoriesCount"],"properties":{"path":{"type":"string","description":"Full path to the directory, with leading and trailing slashes","example":"\/test\/subfolder\/child\/"},"name":{"type":"string","example":"child"},"isFolder":{"type":"boolean","enum":[true],"example":true},"size":{"type":"integer","format":"int64","description":"Total size of files in the directory (recursively), in bytes","example":1048576},"filesCount":{"type":"integer","description":"Number of files in the directory (recursively)","example":4},"directoriesCount":{"type":"integer","description":"Number of nested directories (recursively)","example":1}}},"DirectoryEntryFile":{"type":"object","required":["path","name","isFolder","size","date","extension","thumb","stream"],"properties":{"path":{"type":"string","example":"\/test\/subfolder\/file.txt"},"name":{"type":"string","example":"file.txt"},"isFolder":{"type":"boolean","enum":[false],"example":false},"size":{"type":"integer","format":"int64","description":"File size in bytes","example":12800},"date":{"type":"integer","description":"Last modification time, Unix timestamp in seconds","example":1760529600},"extension":{"type":["string","null"],"description":"MIME type detected from the file name","example":"text\/plain"},"thumb":{"type":["string","null"],"description":"Thumbnail path; `null` if there is no thumbnail","example":null},"stream":{"type":"string","description":"Path for video streaming, see `UploadedFile.stream`","example":"\/test\/subfolder\/file.txt"}}},"EmptyArray":{"type":"array","description":"Always an empty array","maxItems":0,"items":{"type":"string"},"example":[]},"ErrorItem":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","example":403},"message":{"type":"string","example":"Forbidden"}}},"ErrorList":{"type":"object","required":["errors"],"properties":{"errors":{"type":"array","items":{"$ref":"#\/components\/schemas\/ErrorItem"}}}},"UnauthorizedError":{"type":"object","required":["success","error"],"properties":{"success":{"type":"boolean","example":false},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","example":401},"message":{"type":"string","example":"Unauthorized action!"}}}}},"ValidationError":{"type":"object","required":["message","errors"],"properties":{"message":{"type":"string","description":"First validation error message (with a count of other errors, if any)","example":"The path field is required."},"errors":{"type":"object","description":"Validation messages grouped by request field name","additionalProperties":{"type":"array","items":{"type":"string"}},"example":{"path":["The path field is required."]}}}}},"responses":{"Unauthorized":{"description":"The token is expired or its signature is invalid (`ErrorList`) \u2014 get a new token and retry the request; or the token is valid, but no user is resolved from it (`UnauthorizedError`)","content":{"application\/json":{"schema":{"oneOf":[{"$ref":"#\/components\/schemas\/ErrorList"},{"$ref":"#\/components\/schemas\/UnauthorizedError"}]},"examples":{"expiredOrInvalidToken":{"summary":"Token is expired or its signature is invalid","value":{"errors":[{"code":401,"message":"Unauthorized"}]}},"userNotResolved":{"summary":"Token is valid, but no user is resolved from it","value":{"success":false,"error":{"code":401,"message":"Unauthorized action!"}}}}}}},"Forbidden":{"description":"The Authorization header is missing, or the token is malformed or not yet valid","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ErrorList"},"example":{"errors":[{"code":403,"message":"Forbidden"}]}}}},"ValidationFailed":{"description":"Request validation failed","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/ValidationError"}}}}},"securitySchemes":{"oauth2":{"type":"oauth2","flows":{"clientCredentials":{"tokenUrl":"https:\/\/api.sendpulse.com\/oauth\/access_token","scopes":[]}}},"apiKey":{"type":"apiKey","in":"header","name":"Authorization"}}},"security":[{"oauth2":[]},{"apiKey":[]}]}