# M-Files gRPC API: concepts, runtime and guides All concept, runtime and guide pages of the M-Files gRPC API 26.11. Pages are separated by `---` lines. --- # Vault and metadata structure Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/ An M-Files vault is not a file system. It is a database of **objects** (documents, customers, projects, ...) described by **metadata**. Folders are views computed from metadata, files belong to objects, and the meaning of every number in a request depends on which **ID space** it belongs to. Read this page before writing code that creates, searches or updates objects. All calls run inside one vault: the vault is chosen at login (`LoginData.vault_guid` in [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md)) and the session carries it. ## The building blocks ```text Vault |-- Object types and value lists one ID space; read with GetObjectTypes / GetValueLists | |-- 0 Document, 136 Customer, ... real object types: objects with versions | '-- 1 Classes, 6 Users, 154 Countries value lists: items without versions |-- Classes items of value list 1; each belongs to one object type | '-- associated property definitions (required or optional), default workflow |-- Property definitions 0 Name or title, 100 Class, 1002 Document date, ... '-- Workflows (value list 7) -> states (value list 8) -> state transitions (value list 17) Object = object type + ID; each version has a class (property 100) and property values ``` - **Object types** are the kinds of things the vault stores. Read them with [GetObjectTypes](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectTypes/index.md). Each is a [ValueList](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ValueList/index.md) message with `is_real_object_type` true. `options` ([OBJTYPEFLAGS](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.OBJTYPEFLAGS/index.md)) says, for example, whether its objects can have files (`can_have_files`, true for documents). - **Value lists** are lists of simple items, such as Countries or Users. Read them with [GetValueLists](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueLists/index.md). It returns the real object types too, because object types and value lists are the same kind of thing and share one ID space: an object type is a value list whose items are full objects. - **Owners.** A value list or object type can have an owner (`owner_object_type`): in the Sample Vault, Contact person (149) is owned by Customer (136), and Workflow States (8) are owned by Workflows (7). - **Classes** subdivide an object type. Every object version has exactly one class, stored in property 100 (Class), a lookup to value list 1. Read them with [GetObjectClassesAndGroups](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetObjectClassesAndGroups/index.md). An [ObjectClass](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectClass/index.md) names its `object_type`, its `associated_property_defs` (each with `is_required`), the `name_property_def` that supplies the object's title, and its `default_workflow` (`force_workflow` makes it mandatory). - **Class groups** (value list 2) group classes for display only. Property 101 lists them. - **Property definitions** define the fields. Read them with [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md). A [PropertyDef](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyDef/index.md) has a `data_type` ([Datatype](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.Datatype/index.md)); a lookup property also has `value_list`, the value list or object type it points to. `update_type` `UPDATE_TYPE_SET_BY_SERVER` marks values that the server maintains and callers do not set. See [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md). - **Workflows and states** are metadata too: an object's workflow and state are lookup properties 38 and 39. See [Workflows](https://developer.m-files.com/APIs/gRPC-API/concepts/workflows/index.md). ## The ID spaces Most IDs in the API are plain `int32` values, and the same number means different things in different spaces. Mixing them up is the most common mistake. | ID space | Where it appears | Example in the Sample Vault | | --- | --- | --- | | Object type / value list ID | `ObjID.type`, `ValueList.id`, `PropertyDef.value_list` | 136 = Customer | | Object ID, per object type | `ObjID.item_id.internal_id` | customer 142, document 142 | | Value list item ID, per value list | `ItemInfo.obj_id.item_id` in lookups | country 1 = USA | | Class ID (items of value list 1) | property 100, `ObjectVersion.class_id` | 78 = Customer | | Property definition ID | `PropertyValue.property_def`, search expressions | 1079 = Customer | | Workflow, state, transition IDs | items of value lists 7, 8 and 17 | workflow 106, state 135 | | User and user group IDs | items of value lists 6 and 16 | group 1 = All internal users | | Version number, per object | `ObjVerVersion.internal_version` | version 3 | Two traps from the table: - **"Customer" is three different IDs.** In the Sample Vault, object type Customer is 136, the class Customer is 78, and the property definition Customer (a multi-select lookup to object type 136) is 1079. A search for documents of a customer uses property 1079 with a lookup to object 141 of type 136. - **Object IDs are unique only within an object type.** A search for object ID 142 without an object type returns both document 142 and customer 142. Always carry the full [ObjID](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjID/index.md) (type and ID). ## Built-in IDs These IDs are the same in every vault. Everything else, including every ID of 1000 or more seen in the Sample Vault, is assigned when the item is created and can differ between vaults: resolve it by alias or GUID (below) instead of hard-coding it. | Object type / value list | ID | | --- | --- | | Document (object type) | 0 | | Classes | 1 | | Class groups | 2 | | Users | 6 | | Workflows / Workflow states / State transitions | 7 / 8 / 17 | | Document collection / Assignment / Report (object types) | 9 / 10 / 15 | | User groups | 16 | | Property definition | ID | Data type | | --- | --- | --- | | Name or title | 0 | text | | Created / Last modified | 20 / 21 | timestamp, set by the server | | Single file | 22 | Boolean | | Last modified by / Created by | 23 / 25 | lookup to Users | | Deleted / Deleted by | 27 / 28 | set by the server | | Comment (version comment) | 33 | multi-line text | | Workflow / Workflow State | 38 / 39 | lookup to value list 7 / 8 | | Moved into current state | 40 | timestamp, set by the server | | Assigned to | 44 | multi-select lookup to Users | | State transition | 99 | lookup to value list 17 | | Class / Class groups | 100 / 101 | lookup to value list 1 / 2 | Built-in classes include 0 (Document, the unclassified document), 1 (Other Document), -100 (Assignment) and -101 (Report). Built-in named ACL -1 is "Full control for all internal users"; user group 1 is "All internal users". ## Aliases and GUIDs Code that must work in more than one vault identifies structure items by **alias** or **GUID** and resolves them to IDs at run time. - **GUIDs.** Every object type, property definition and value list item has a GUID (`guid` in [ValueList](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ValueList/index.md), [PropertyDef](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyDef/index.md) and [ItemInfo](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ItemInfo/index.md)). [GetMetadataStructureItemIdByGUID](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdByGUID/index.md) resolves one: `item_type` `METADATA_STRUCTURE_ITEM_OBJECT_TYPE` with the Customer GUID returns `is_found` true and `id` 136. - **Aliases** are names an administrator gives to structure items, for example `MyCompany.Property.InvoiceNumber`. They are stored as a semicolon-separated list in the admin structures, such as `PropertyDefAdmin.aliases`. - [GetMetadataStructureItemIdByAlias](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdByAlias/index.md) resolves one alias of one [MetadataStructureItem](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.MetadataStructureItem/index.md) type. Matching ignores case and surrounding spaces. It returns `id` -1 when no item, or more than one item, has the alias; for named ACLs it returns -1000 instead, because -1 is a real named ACL. - [GetAllMetadataStructureItemIdswithAliases](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetAllMetadataStructureItemIdswithAliases/index.md) returns all alias-to-ID mappings in one call (`METADATA_STRUCTURE_ITEM_ALL` for every type). Set `unambiguous_aliases_only` to true: false fails with "Not implemented." - The Sample Vault defines no aliases; both methods return nothing there. Resolve aliases and GUIDs once, and cache the IDs as described in [Metadata structure version and caching](https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/index.md). ## Reading the structure | To get | Call | | --- | --- | | Object types | [GetObjectTypes](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectTypes/index.md) | | Value lists and object types | [GetValueLists](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueLists/index.md) | | Items of a value list | [GetValueListItemsWithPermissions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueListItemsWithPermissions/index.md) | | Property definitions | [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md) | | Classes and class groups | [GetObjectClassesAndGroups](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetObjectClassesAndGroups/index.md) | | Workflows, states, transitions | [GetWorkflowsForClient](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowsForClient/index.md), [GetWorkflowStatesAndTransitions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowStatesAndTransitions/index.md) | | Named ACLs | [GetNamedACLs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCNamedACLs/GetNamedACLs/index.md) | Some of these responses depend on the caller: they carry the caller's permissions, for example `ObjectClass.current_user_permissions`. --- # Objects and versions Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/ Everything stored in a vault is an **object** of some object type: a document, a customer, a project. Changes are made in new **versions** of the object; older versions stay readable, and most read calls ask for one specific version. ## Identity: ObjID and ObjVer ```text ObjID type: 0 object type (0 = Document) item_id { internal_id: 358 } object ID within that type ObjVer obj_id { type: 0 item_id { internal_id: 358 } } version { type: OBJ_VER_VERSION_TYPE_SPECIFIC internal_version: 3 } ``` - [ObjID](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjID/index.md): `type` is the object type ID and `item_id.internal_id` the object's ID. IDs are unique only within an object type: document 142 and customer 142 are different objects. Never pass an object ID without its type. - [ObjVer](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjVer/index.md): an `ObjID` plus an [ObjVerVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjVerVersion/index.md). Version numbers grow with each change. - [ObjectVersionEx](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectVersionEx/index.md) is what reads and searches return: version information in `version_info` ([ObjectVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectVersion/index.md): version, title, class, files) and object-level information in `object_info` ([ObjectLevelInfo](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectLevelInfo/index.md): ID, GUID, check-out state, flags). ## Latest or specific version `ObjVerVersion.type` says what `internal_version` means. Versions returned by the server are always `OBJ_VER_VERSION_TYPE_SPECIFIC` with a number. Requests should send the same: - [GetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetProperties/index.md) and [GetObjectFiles](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectFiles/index.md) need a specific version. `OBJ_VER_VERSION_TYPE_LATEST` (which the server reads as version -1), `OBJ_VER_VERSION_TYPE_ANY` and an unset version all fail with "Not found." - To read the latest version, use a method with its own "latest" switch: [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md) (`latest_version`), [GetObjectVersionAndPropertiesOfMultipleObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectVersionAndPropertiesOfMultipleObjects/index.md) (`latest_versions`), [GetObjectInfo](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfo/index.md) (`latest_version`); or get the number first with [GetLatestObjectVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetLatestObjectVersion/index.md). - Lookup values are the exception: a lookup to an object normally refers to its latest version, and the server returns such lookups with `OBJ_VER_VERSION_TYPE_LATEST` and -1. "Latest" means the latest checked-in version, except for the user who has the object checked out: with `allow_checked_out` true (and in `GetObjectInfo`), that user gets the checked-out version. ## Check-out and check-in An object is changed through a **checked-out version**: a new version that only the user who checked it out can see and edit until it is checked in. ```text AddObject(check_in: false) v1 checked out to you others: "Not found." CheckIn(v1) v1 checked in everyone with access sees v1 CheckOut v2 checked out to you others still see v1 SetProperties(v2), ... still v2 edits do not add versions CheckIn(v2) v2 checked in (or UndoCheckout(v2): v2 is discarded) SetProperties(v2, allow_modifying_checked_in_object: true) v3 checked in in one call check-out, change, check-in ``` The sequence above was run against the Sample Vault. Points to remember: - [CheckOut](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckOut/index.md) takes an `ObjID` and returns the new version; `previous_version` is the version it was made from. `ObjectLevelInfo.checked_out_version` and `checked_out_to_user_id` show the check-out state; `checked_out_version` is 0 when the object is not checked out. - While a user has the object checked out, other users cannot change it: `CheckOut` and `SetProperties` fail with "The object is checked out to another user." They cannot read the checked-out version either. - [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) on a checked-in version fails with "The object is not checked out." unless `allow_modifying_checked_in_object` is true. Add `fail_if_newer_version_exists` to make the call fail ("There is a newer version of this object on the server.") when someone else has changed the object since you read it. - [UndoCheckout](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UndoCheckout/index.md) discards the checked-out version. - An object created with `check_in` false is visible only to its creator until it is checked in, and `GetLatestObjectVersion` with `allow_checked_out` false answers "No checked-in version available." - Give a new object an ACL when you create it: without one it gets an empty ACL and, once checked in, only users with the vault-level right to see all objects can read it, not even its creator. See [Permissions](https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/index.md#give-new-objects-an-acl). ## Files: single-file and multi-file documents Files belong to object versions; there is no file without an object. Object types whose `OBJTYPEFLAGS.can_have_files` is true (documents, for example) can carry files, and their objects must have property 22 (Single file): - **Single-file document** (`Single file` true, `ObjectVersion.is_single_file_object` true): exactly one file. - **Multi-file document** (`Single file` false): any number of files, including none. Creating a document without property 22 fails with 'The mandatory property value "Single file" is missing.' Each [File](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.File/index.md) in `ObjectVersion.files` has a [FileVer](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.FileVer/index.md): a file ID and a file version. Files have versions of their own, separate from the object's version numbers. How to download and upload file content is described in [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md). ## Deleted and destroyed | | Deleted ([RemoveObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/RemoveObject/index.md)) | Destroyed ([DestroyObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DestroyObject/index.md)) | | --- | --- | --- | | Data | kept; `ITEMFLAGS.is_deleted` set | removed permanently | | Normal users | "Not found."; not in search results | "Not found." | | Users with `see_deleted_objects` | visible; found by searches without a "not deleted" condition | "Not found." | | Way back | [UndeleteObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UndeleteObject/index.md) | none | Undeleting and destroying need the vault-level rights `undelete_objects` and `destroy_objects` (see [Permissions](https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/index.md)), and seeing deleted objects needs `see_deleted_objects`. In the Sample Vault a normal user could delete an object but got "Access denied. You do not have the access rights required by this operation." for undelete and destroy; a vault administrator could do both. `DestroyObject` takes `all_versions` true with version type `OBJ_VER_VERSION_TYPE_ALL`, or a single version. ## GUIDs and external IDs - **Object GUID**: `ObjectLevelInfo.guid`, or [GetObjectGUID](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectGUID/index.md). [GetObjIDByGUID](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjIDByGUID/index.md) maps it back to an `ObjID`. Each version also has its own GUID (`ObjectVersion.version_guid`). - **External ID**: `ObjectLevelInfo.external_id` and `external_id_status` ([ExtIDStatus](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.ExtIDStatus/index.md)) link an object to a record in an external database. For objects created in the vault the status is `EXT_IDSTATUS_INTERNAL`. The `STATUS_TYPE_EXTERNAL_ID` search condition finds objects by external ID. Store the `ObjID` (or the GUID) of an object, not its title or a version number: titles change, and every edit makes a new version. --- # Properties and values Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/ The metadata of an object version is a list of property values. Each one pairs a property definition ID with a typed value: ```text PropertyValue property_def: 1002 property definition ID ("Document date") value: TypedValue type: DATATYPE_DATE must match PropertyDef.data_type is_null_value: false true = empty value; data is then unset data: TypedValueUnion { date: ... } exactly one member, the one that matches type ``` - [PropertyValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyValue/index.md) carries `property_def`, an ID from the property definition space (see [the ID spaces](https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/index.md#the-id-spaces)). - [TypedValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValue/index.md) carries the data type in `type` and the value in one member of [TypedValueUnion](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValueUnion/index.md). Set both, and pick the member that matches `type`; a `DATATYPE_TEXT` value sent in `multi_line_text` fails with "The parameter is incorrect." - The type must also match the property definition's `data_type` (read with [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md)). A text value for the date property 1002 fails with 'Wrong data type for property definition "Document date".' ## Data types | Data type | Union member | Example property (Sample Vault) | Notes | | --- | --- | --- | --- | | `DATATYPE_TEXT` | `text` | 0 Name or title, 1004 Keywords | at most 100 characters; longer text is cut to 100 without an error | | `DATATYPE_MULTI_LINE_TEXT` | `multi_line_text` | 33 Comment, 1034 Description | long text; `\n` line breaks are kept | | `DATATYPE_INTEGER` | `integer` | 1103 Proposal number | 32-bit | | `DATATYPE_REAL_NUMBER` | `real_number` | (none in the Sample Vault) | double | | `DATATYPE_DATE` | `date` | 1002 Document date | a `Timestamp` at midnight UTC; a time of day is dropped | | `DATATYPE_TIMESTAMP` | `timestamp` | 20 Created, 21 Last modified | UTC `Timestamp` | | `DATATYPE_BOOLEAN` | `boolean` | 22 Single file, 1098 Accepted | | | `DATATYPE_LOOKUP` | `lookup` | 100 Class, 39 Workflow State, 1143 Agreement type | one item or object | | `DATATYPE_MULTI_SELECT_LOOKUP` | `multi_select_lookup` | 1079 Customer, 101 Class groups | any number of items or objects | [Datatype](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.Datatype/index.md) lists more types, such as `DATATYPE_TIME`; the Sample Vault has no properties of them. By their descriptions, `DATATYPE_INTEGER64` and `DATATYPE_FILETIME` are not used in document properties. The 100-character limit is easy to miss: the server stores the first 100 characters of a longer single-line text and reports success. Use a multi-line text property for longer values. ## Empty values An empty value keeps its type: `type` set, `is_null_value` true, `data` unset. The server returns empty properties the same way, for example `1034 Description` on an object without a description: ```text property_def: 1034 value { type: DATATYPE_MULTI_LINE_TEXT is_null_value: true } ``` Send the same shape to empty a property. Removing a property from the object entirely is a different operation (`remove` in [SetPropertiesRequest](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md#request)). It works only for properties that the class does not list: removing one that it lists fails with 'The property "Keywords" cannot be removed from the object.' A property that the class lists is present on the object even when nobody set it, as an empty value. ## Lookups A lookup value points to a value list item or an object. To set one, only the IDs are needed: ```text property_def: 1143 Agreement type (lookup to value list 160) value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { type: 160 item_id { internal_id: 2 } } } } } } ``` - `value_list_item_info.obj_id.type` must be the property definition's `value_list` (for a lookup to objects, the object type ID: 136 for property 1079 Customer). Any other value fails with "The parameter is incorrect." followed by the IDs, for example "(136-2, 160)". - `item_id.internal_id` is the item or object ID in that list. An ID that does not exist fails with 'The value "9999, referencing object type: 0" does not exist, or it is a conflict object.' - A multi-select lookup holds a [Lookup](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.Lookup/index.md) per selected item in `multi_select_lookup.values`. - When you read values, the server fills in the display data: `name` (the item's title, which is what a user sees), `guid`, `external_id`, and `version` with `OBJ_VER_VERSION_TYPE_LATEST` (-1). You do not need to send these. ## Rules the server applies - **Required properties.** A class lists its properties in `ObjectClass.associated_property_defs`, each with `is_required`. Creating or saving an object without a required value fails with 'The mandatory property value "Document date" is missing.' - **Class first.** Property 100 (Class) decides which properties are expected, the title source and the workflow rules. Every object version has it. - **The title.** Property 0 (Name or title) always holds the object's title. When a class uses another property as its name (`ObjectClass.name_property_def`, such as 1144 Agreement title for "Contract or Agreement"), property 0 holds the same value. - **Server-maintained values.** Properties whose `PropertyDef.update_type` is `UPDATE_TYPE_SET_BY_SERVER` (Created, Last modified, Created by, sizes, ...) are set by the server. Properties with an `automatic_value_type` other than `AUTOMATIC_VALUE_TYPE_NONE` (in the Sample Vault: 1102 Proposal title, calculated from a template; 1103 Proposal number, an incrementing number) get their values automatically. - **Workflow and state** are ordinary lookup properties (38 and 39) with extra rules; see [Workflows](https://developer.m-files.com/APIs/gRPC-API/concepts/workflows/index.md). ## Writing values | Task | Call | | --- | --- | | Create an object with values | [AddObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObject/index.md) (`properties`) | | Change values | [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) (`properties`, `remove`) | | Change values when checking in | [CheckIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckIn/index.md) (`properties`) | | Read values | [GetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetProperties/index.md), [GetObjectVersionAndPropertiesOfMultipleObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectVersionAndPropertiesOfMultipleObjects/index.md) | `SetProperties` changes only the properties you send, unless `remove_unspecified_properties` is true: then every property not in the request is removed. Leave it false unless you send the complete set. Methods whose names end in `WithMetadata` use [PropertyValueWithMetadata](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyValueWithMetadata/index.md) instead, which adds `value_metadata` (who or what set the value, such as a user or AI, and why). The plain `PropertyValue` methods above do not need it. --- # Search Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/search/ There are no folders to walk in a vault. To find objects you describe them with conditions and call [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md) (or [GetObjectCountInSearch](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/GetObjectCountInSearch/index.md) to count them). A search takes one array of conditions that must all match, returns only objects the caller may see (for users who may see deleted objects, also deleted ones unless a condition excludes them), and returns no property values. The methods are in the [IRPCSearch](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/index.md) reference; the [search guide](https://developer.m-files.com/APIs/gRPC-API/guides/search/index.md) builds a complete search step by step. ## The condition model ```text SearchObjectsRequest conditions: [ SearchConditionArray ] exactly one array value: [ SearchCondition, ... ] all conditions must match (AND) expression what to test property 100 | status "is deleted" | any field (full text) type how to compare CONDITION_TYPE_EQUAL | _CONTAINS | _GREATER_THAN | ... value compare to what a TypedValue, like a property value ``` - A [SearchCondition](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchCondition/index.md) is an [Expression](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.Expression/index.md), a [ConditionType](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.ConditionType/index.md) and a [TypedValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValue/index.md). - The conditions of a [SearchConditionArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchConditionArray/index.md) are ANDed. An array with no conditions matches every object the caller can see. - Although `conditions` is a repeated field, `SearchObjects` accepts **exactly one** array: none, or two or more, fail with "The parameter is incorrect." The same holds for `GetObjectCountInSearch`. - **OR within one property:** compare a lookup property `CONDITION_TYPE_EQUAL` to a multi-select lookup value that lists the alternatives. Class one-of (1, 2) in the Sample Vault returned 55 objects, the 39 + 16 of the two classes. For other OR cases, run one search per alternative. Expression kinds ([ExpressionType](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.ExpressionType/index.md)): | Expression | Set | Use for | | --- | --- | --- | | `EXPRESSION_TYPE_PROPERTY_VALUE` | `data.property_value.property_def` | a property: class, title, dates, lookups | | `EXPRESSION_TYPE_STATUS_VALUE` | `data.status_value.type` ([StatusType](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.StatusType/index.md)) | object type, ID, deleted, checked out | | `EXPRESSION_TYPE_ANY_FIELD` | `data.any_field.options` ([FTSFLAGS](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.FTSFLAGS/index.md)) | full-text search, Aino search | | `EXPRESSION_TYPE_TYPED_VALUE` | `data.typed_value` | any lookup to a value list, whichever property holds it | | `EXPRESSION_TYPE_FILE_VALUE` | `data.file_value.type` | file name, size, has files | An expression can also follow lookups to other objects ("documents whose project's customer is X") with `Expression.indirection_levels`; see [IndirectionLevel](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.IndirectionLevel/index.md). ## Conditions you need most Shown in protobuf text format. IDs above 1000 are from the Sample Vault. ```text # Object type is Document (0): a lookup to the object type. expression { type: EXPRESSION_TYPE_STATUS_VALUE data { status_value { type: STATUS_TYPE_OBJECT_TYPE } } } type: CONDITION_TYPE_EQUAL value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { item_id { internal_id: 0 } } } } } } # Not deleted. Add this to almost every search (see "What comes back"). expression { type: EXPRESSION_TYPE_STATUS_VALUE data { status_value { type: STATUS_TYPE_IS_DELETED } } } type: CONDITION_TYPE_EQUAL value { type: DATATYPE_BOOLEAN data { boolean: false } } # Class (property 100) is 2, "Purchase Invoice". expression { type: EXPRESSION_TYPE_PROPERTY_VALUE data { property_value { property_def: 100 } } } type: CONDITION_TYPE_EQUAL value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { type: 1 item_id { internal_id: 2 } } } } } } # Customer (property 1079, a multi-select lookup) includes customer 141. A single lookup # value matches objects whose multi-select value contains it. expression { type: EXPRESSION_TYPE_PROPERTY_VALUE data { property_value { property_def: 1079 } } } type: CONDITION_TYPE_EQUAL value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { type: 136 item_id { internal_id: 141 } } } } } } # Title (property 0) contains "ups". Text comparisons ignore case. expression { type: EXPRESSION_TYPE_PROPERTY_VALUE data { property_value { property_def: 0 } } } type: CONDITION_TYPE_CONTAINS value { type: DATATYPE_TEXT data { text: "ups" } } # Last modified (property 21) on or after 2012-01-01T00:00:00Z. expression { type: EXPRESSION_TYPE_PROPERTY_VALUE data { property_value { property_def: 21 } } } type: CONDITION_TYPE_GREATER_THAN_OR_EQUAL value { type: DATATYPE_TIMESTAMP data { timestamp { seconds: 1325376000 } } } # One object: type 136, ID 142. STATUS_TYPE_OBJECT_ID alone would also match document 142. expression { type: EXPRESSION_TYPE_STATUS_VALUE data { status_value { type: STATUS_TYPE_OBJECT_TYPE_AND_ID } } } type: CONDITION_TYPE_EQUAL value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { type: 136 item_id { internal_id: 142 } } } } } } # Full text in metadata and file contents. expression { type: EXPRESSION_TYPE_ANY_FIELD data { any_field { options { search_metadata: true search_filedata: true } } } } type: CONDITION_TYPE_CONTAINS value { type: DATATYPE_TEXT data { text: "invoice" } } ``` A full-text condition must set `search_metadata`, `search_filedata` or both; with neither, the call fails with "Invalid search condition." Setting `FTSFLAGS.search_aino` instead runs an Aino (semantic) search, which needs an enabled Aino index in the vault: without one the call fails with "An enabled Aino index must be present to make an Aino Search." Aino results carry the matching text passage in `SearchResultsItem.chunk`, but not the class or the object version, and their file version is reported as 1: read the objects before using those values (see [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md)). ## What comes back - **Only what the caller may see.** Results are filtered by permissions without any error: the same class search returned 0 contracts to a normal user and 13 to an administrator. - **Deleted objects, for some users.** A user who can see deleted objects (a vault administrator, for example) gets them too unless the conditions exclude them: 66 versus 65 results for one class in the Sample Vault. Add the "not deleted" condition. - **One version per object**: the latest checked-in version, or the checked-out version when the object is checked out to the caller. `SearchFlags.look_in_all_versions` searches all versions instead. - **No property values.** Each [SearchResultsItem](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchResultsItem/index.md) has an `ObjectVersionEx` (IDs, version, title, class, files) and a relevance `score`. Read properties with [GetObjectVersionAndPropertiesOfMultipleObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectVersionAndPropertiesOfMultipleObjects/index.md). ## Limits, timeouts, sorting and paging - `limit`: the maximum number of results; 0 means no limit, apart from a maximum the vault administrator may configure. `has_more_results` is true when more objects matched than were returned. The Sample Vault had 226 documents for a normal user: `limit` 10 returned 10 with `has_more_results` true; `limit` 0 returned all 226. - `timeout_in_seconds`: 0 means no timeout, apart from a configured maximum. - `sorting` ([SearchResultsSorting](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchResultsSorting/index.md)): one [SortRule](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SortRule/index.md) at most. `key` is a property definition ID (21 sorts by last modified; `sort_descending` reverses), or -102 for the object ID (ascending only). An unsupported sort (two rules, a multi-line text property, -102 descending) fails with "The content can't be sorted across all pages." unless `options.allow_resorting_to_default_sort` is true; then the results come in relevance order and the response has `resorted_to_default_sort` set. - **Paging.** `page_requests` is not supported ("Not implemented."). Page with the object ID instead: sort by key -102 and add a `STATUS_TYPE_OBJECT_ID` condition `CONDITION_TYPE_GREATER_THAN` the last ID received (start with 0), until `has_more_results` is false. With `limit` 100 this read the 226 documents in pages of 100, 100 and 26. Sorting by object ID does not work together with a full-text condition. The [paging guide](https://developer.m-files.com/APIs/gRPC-API/guides/paging/index.md) has the complete loop. - `facet_filter` narrows a full-text search by facet values for UIs that show facets; it is JSON and works with [GetFacetValues](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/GetFacetValues/index.md). ## Errors Search errors come back with gRPC status `UNKNOWN` and the M-Files message as the status details, for example "Invalid search condition." or "The parameter is incorrect." ([Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md) shows how to read the error code). A condition built with the wrong ID space usually does not fail: it just finds nothing, or finds the wrong objects. When a search returns nothing, check the IDs against [the ID spaces](https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/index.md#the-id-spaces) first. --- # Workflows Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/workflows/ A workflow is a state machine that an object moves through, such as Draft, Waiting for Approval, Approved. In M-Files the workflow and the state are **property values** of the object version, so moving an object to another state is a property change that the server checks against the workflow's rules. ## The pieces | Item | Value list | Object property | | --- | --- | --- | | Workflow | 7 (Workflows) | 38 Workflow, a lookup to value list 7 | | State | 8 (Workflow States), owned by value list 7 | 39 Workflow State, a lookup to value list 8 | | State transition | 17 (State transitions) | 99 State transition, a lookup to value list 17 | | Time of the last state change | | 40 Moved into current state, set by the server | Each state belongs to one workflow. State IDs and transition IDs are their own ID spaces: state 135 and transition 2 below are unrelated numbers. ```text Sample Vault workflow 106 "Reviewing drawings" (no state) --[1]--> 135 Listed for approval --[2]--> 136 Approved ^ | '----------[3]-------------' ``` Classes link to workflows: `ObjectClass.default_workflow` proposes one, and with `force_workflow` true every object of the class must use it. In the Sample Vault, class 2 (Purchase Invoice) forces workflow 102; creating an invoice without it fails with 'The specified workflow "(empty)" is not allowed for this class.' A workflow's `object_class_id` in [Workflow](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.Workflow/index.md) is -3 when it is meant for all classes. ## Reading workflows and the allowed transitions - [GetWorkflowsForClient](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowsForClient/index.md) lists the workflows. - [GetWorkflowStatesAndTransitions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowStatesAndTransitions/index.md) returns the states of one workflow and the transitions **out of `current_state`**. Without `current_state` it returns the states and no transitions. Each [StateTransitionBasic](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.StateTransitionBasic/index.md) has `from_state_id`, `to_state_id` and `can_current_user_select`. - Pass `obj_ver` too when the transition permissions depend on the object (for example "the object's owner may approve"). Without it, transitions whose permissions refer to such object-specific users are reported as selectable, and `are_state_transition_permissions_object_specific` tells you that the flag cannot be trusted. - [GetWorkflowStateHistory](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowStateHistory/index.md) returns the object's workflow state history as object versions with their property values. - The full definitions (state actions, preconditions, transition permissions, signature settings) come from [GetWorkflows](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCWorkflowsAdmin/GetWorkflows/index.md), which needs administrator rights. ## Changing the state Set property 39 to the new state, in any call that writes properties: [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md), [CheckIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckIn/index.md) or [AddObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObject/index.md). ```text SetPropertiesRequest obj_ver { obj_id { type: 0 item_id { internal_id: 546 } } version { type: OBJ_VER_VERSION_TYPE_SPECIFIC internal_version: 1 } } allow_modifying_checked_in_object: true properties { property_def: 39 value { type: DATATYPE_LOOKUP data { lookup { value_list_item_info { obj_id { type: 8 item_id { internal_id: 136 } } } } } } } ``` Run against the Sample Vault, this moved a document from state 135 to 136 and created version 2 with property 40 set to the time of the change. Alternatively, set property 99 to a transition ID: the server then sets property 39 to the transition's target state. The server checks the change and answers with an error when it is not allowed (messages shortened; the server puts line breaks between their parts): | Situation | Error message | | --- | --- | | No transition leads from the current state to the new one, or the caller may not use it | 'Access denied. The state transition "(no state) -> Approved" is not allowed.' | | The new state's preconditions are not met | "The preconditions specified for the new state are not satisfied. ..." followed by the failing condition | | The class forces a different workflow | 'The specified workflow "(empty)" is not allowed for this class.' | To start an object in a workflow, set property 38 and the first state in property 39 together. Setting only property 38 leaves the state empty. A state can carry actions that run when an object enters it. In the Sample Vault, the first state of the invoice workflow is defined to replace the object's permissions and to assign the object to a user, so a state change can change who can see the object. ## Electronic signatures A state transition can require an electronic signature, for example for regulated approvals. [GetStateTransitionSignatureSettings](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetStateTransitionSignatureSettings/index.md) returns the transition's [ElectronicSignatureSettings](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ElectronicSignatureSettings/index.md) (`is_required`, the reason and meaning to show, where the signature is stored). Requests that change properties have a `signature` field for the signature; the remarks of [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) describe what it holds. The workflows of the Sample Vault require no signatures. The [workflow state guide](https://developer.m-files.com/APIs/gRPC-API/guides/workflow-state/index.md) puts these calls together. --- # Permissions Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/ Every call runs as the user who logged in. What that user may not see is silently left out of searches and listings, reading it usually fails with "Not found." or "Access denied.", and an object created without an ACL ends up invisible even to its creator: always send an ACL when you create objects. The server checks two kinds of rights: ```text vault-level rights what the user may do in the vault at all (LogIn response: session_data.access_rights.vault_access) create_objects, see_all_objects, see_deleted_objects, undelete_objects, destroy_objects, change_metadata_structure, manage_workflows, ... object permissions what the user may do with one object version (the version's AccessControlList) read, edit, delete, change permissions ``` Value list items, object types, classes and property definitions carry ACLs of their own (the `acl` fields of [ValueList](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ValueList/index.md), [ObjectClass](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectClass/index.md) and [PropertyDef](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyDef/index.md)), with the caller's resulting rights in fields such as `ObjectClass.current_user_permissions`. ## Object ACLs An [AccessControlList](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.AccessControlList/index.md) has: - `custom_component` ([AccessControlListComponent](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.AccessControlListComponent/index.md)): either a reference to a **named ACL** (`has_named_acl` true and `named_acl_id`) or explicit entries in `permissions`, each an [AccessControlEntry](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.AccessControlEntry/index.md) with a subject (a user, a user group, or a pseudo-user such as "the user in the object's Owner property") and the permissions granted. - `restrictive_components` and `additive_components`: automatic permissions, which an object gets through lookup values to items that pass their permissions on (see `PropertyDef.enable_automatic_permissions`). - `checked_out_to_user`: the user who has the object checked out and therefore extra rights to it while the check-out lasts. **Named ACLs** are reusable, named permission sets. List them with [GetNamedACLs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCNamedACLs/GetNamedACLs/index.md), with `include_builtin` true and `types` set to the [NACLType](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.NACLType/index.md) values you want as bits (1 for normal named ACLs); `types` 0 returns nothing. The built-in named ACLs include -1 "Full control for all internal users" and -20 "Only for me". The Sample Vault adds its own, such as 1 "Visible to company management only". ## Give new objects an ACL [AddObjectWithFiles](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObjectWithFiles/index.md) and [AddObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObject/index.md) do not compute a default ACL from the class or object type. **An unset `acl` creates an object with an empty ACL**: no entries, so once the object is checked in, only users with the vault-level right to see all objects (vault administrators, for example) can read it, **not even its creator**. In the Sample Vault, a normal user who created a document without an ACL could no longer read it after checking it in: `AddObject` answered with `latest_visible` false, and the `created_object` that `AddObjectWithFiles` returned had no title or properties. Send a named ACL reference instead; the server fills in its entries: ```text acl { custom_component { has_named_acl: true named_acl_id: -1 } } ``` The document created this way stored group 1 (All internal users) with full control, and its creator could read it. A reference alone works for named ACLs that do not depend on the current user. For "Only for me" (-20), a reference alone leaves the object without permissions: send the named ACL's full `acl_for_objects` from `GetNamedACLs` instead, which names the user. To have the server recalculate an existing object's ACL from its property values, call [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) with `acl_mode` `ACLENFORCING_MODE_RESET_TO_DEFAULT` ([ACLEnforcingMode](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.ACLEnforcingMode/index.md)); in the Sample Vault that also produced named ACL -1. ## Reading permissions | Question | Call | | --- | --- | | What may I do in this vault? | `session_data.access_rights` in the [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) response | | What is the ACL of this object version? | [GetSecurity](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetSecurity/index.md) | | Who can do what with this object version? | [GetEffectivePermissions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetEffectivePermissions/index.md) | | Would an ACL give me some access? | [CheckAccess](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckAccess/index.md) | | Change an object's ACL | [ChangeSecurity](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/ChangeSecurity/index.md) | | Value list item permissions | [GetValueListItemsWithPermissions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueListItemsWithPermissions/index.md) with `include_permissions` | `GetEffectivePermissions` lists every user who has access, one [EffectivePermissions](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.EffectivePermissions/index.md) per user ID with `can_user_see`, `can_user_edit`, `can_user_delete` and `can_user_change_permissions`; a normal user can call it. ## What "access denied" looks like A caller who lacks rights sees one of three outcomes: 1. **Silence.** Searches and listings leave out what the caller may not see. In the Sample Vault the same class search returned 0 contracts to a normal user and 13 to an administrator. An object history the caller may not read comes back empty. 2. **"Not found."** Reading an object the caller cannot see at all, such as an object checked out to another user and never checked in, or a deleted object, fails like a missing object. 3. **"Access denied."** Acting on something the caller can identify but lacks rights for. Errors arrive with gRPC status `UNKNOWN`; the status details hold the message. The trailer `mfiles-error-serialized-bin` holds an [RPCErrorInfoArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.RPCErrorInfoArray/index.md) whose `error_code` values identify the error (`int32`, so 0x800407E4 reads as -2147219484). An access denial includes 0x800407E4, usually with a second code for the reason: | Case (seen in the Sample Vault) | Message | Reason code | | --- | --- | --- | | No access to the object version | "Access denied. You do not have sufficient access rights to the object." | 0x800400AA | | Missing vault-level right (an admin service, undelete, destroy) | "Access denied. You do not have the access rights required by this operation." | 0x800400A9 | | Workflow state transition not allowed | 'Access denied. The state transition "..." is not allowed.' | 0x800400C3 | | Object or version not visible, or not existing | "Not found." | 0x8004000B (no 0x800407E4) | The messages contain a line break after "Access denied." An expired or invalid session is the exception: it arrives as gRPC status `UNAUTHENTICATED` (see [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#when-a-call-answers-unauthenticated)). Treat access denied as final for the current user and request: retrying the same call does not help. [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md) shows how to decode the error trailer and lists the other common codes. --- # Metadata structure version and caching Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Concepts](https://developer.m-files.com/APIs/gRPC-API/index.md#concepts) HTML version: https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/ Almost every client needs the metadata structure: object types, classes, property definitions, workflows. It changes rarely, so read it once and cache it. The server tells you when to drop the cache: the vault's **metadata structure version** changes whenever the structure changes. ## The metadata structure version The metadata structure version is a 32-bit counter. It reaches you in two ways: - **In the trailer of most calls.** Methods with `returns_metadata_structure_version` in their `(mfiles.api.method)` options (about 260 of the published methods, including [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md), the object operations and the structure reads) send the trailing metadata `mfiles-metadata-version-bin`: 4 bytes, a little-endian signed 32-bit integer. The Sample Vault sent `00 04 00 00`, which is 1024. - **On request.** [GetMetadataStructureVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureVersion/index.md) returns it in `metadata_structure_version` (1024 in the Sample Vault, the same value as the trailer), plus `property_defs_version`, a separate counter for property definitions. This call itself sends no trailer. gRPC libraries expose the trailer after the call completes, as the call's trailing metadata (in Python, call the method with `with_call()`; the second return value has `trailing_metadata()`). Most libraries hand you `-bin` values already base64-decoded, as raw bytes. A failed call sends no metadata structure version trailer. [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md) lists all metadata keys. The server increments the version when the structure changes, for example when an administrator changes object types or value list definitions, property definitions, classes and class groups, workflows, named ACLs or languages. It does **not** change when objects change or when value list items are added: adding an item to the Countries list left it at 1024. ## Finer-grained versions Some reads have their own version number, which lets you skip unchanged data entirely: | Method | Request field | Response field | | --- | --- | --- | | [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md) | `known_version` | `current_version` | | [GetObjectClassesAndGroups](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetObjectClassesAndGroups/index.md) | `known_version` | `current_version` | | [GetValueListItemsWithPermissions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueListItemsWithPermissions/index.md) | `known_version`, `allow_delta` | `current_version`, `is_delta`, `removed_items` | Send the `current_version` you received last time as `known_version`. If nothing has changed, the response repeats the same `current_version` and contains **no items**: an empty list here means "unchanged", not "empty". In the Sample Vault, `GetPropertyDefs` with `known_version` 483 returned 0 property definitions; Countries with `known_version` 17 returned 0 items, and after an item was added, `allow_delta` true returned just that item with `is_delta` true and version 18. Value list items are not covered by the metadata structure version, so this is how to keep a cached value list current. ## What to cache The metadata structure version tracks the structure only, not object data. Cache by these rules: | Cache until the metadata structure version changes | Do not cache on the metadata structure version | | --- | --- | | [GetObjectTypes](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectTypes/index.md), [GetValueLists](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCValueLists/GetValueLists/index.md) | object reads: properties, files, versions, searches | | [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md), [GetObjectClassesAndGroups](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetObjectClassesAndGroups/index.md) | value list items (use their own version) | | [GetWorkflowsForClient](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowsForClient/index.md), [GetWorkflowStatesAndTransitions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowStatesAndTransitions/index.md) without `obj_ver` | anything that writes | | [GetNamedACLs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCNamedACLs/GetNamedACLs/index.md) | permissions of objects | | [GetMetadataStructureItemIdByAlias](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdByAlias/index.md), [GetMetadataStructureItemIdByGUID](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdByGUID/index.md) | | ## A safe structure cache ```text start: v = GetMetadataStructureVersion().metadata_structure_version load the structure you need; store it under key (vault, user, language, v) every call: if the trailer mfiles-metadata-version-bin is present and differs from v: drop the cached structure; set v to the new value; reload on next use value lists: refresh with known_version / allow_delta when you need current items ``` Key the cache by user and language, not only by vault: structure responses carry the caller's permissions (for example `ObjectClass.current_user_permissions`) and names in the session's language. A cache shared between users can show one user's view of the structure to another. Hold IDs that you resolved from aliases or GUIDs in the same cache. They change only with the structure, so a changed metadata structure version is also the signal to resolve them again. --- # Connecting to the server Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/ M-Files Server speaks native gRPC over HTTP/2. Every service of the API is served on the same endpoint; there is no per-vault address. The vault is chosen when you log in ([Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md)). ## Endpoints | Deployment | Address | Transport | |---|---|---| | M-Files Cloud | `:443`, for example `contoso.cloudvault.m-files.com:443` | TLS, public certificate | | On-premises, default | `:7766` | Plain HTTP/2 (h2c) unless TLS is enabled | | On-premises, TLS enabled | `:7766` | TLS with the server certificate configured in M-Files | [Deployments](https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/index.md) compares the two in one table. **Method path.** Every call is an HTTP/2 POST to `/MFiles./`, for example `/MFiles.IRPCQueryVersion/QueryVersion`. The package is `MFiles` and the service names are the wire names (`IRPCSearch`, `IRPCObjectOperations`). Stubs generated from the published `.proto` files produce these paths; you never write them by hand. On M-Files Cloud the path is used at the root of the vault host, with no prefix. A method the server does not have answers `UNIMPLEMENTED`. ## On-premises: finding the port The gRPC port is the registry value `gRPCEndpoint` (DWORD) under `HKEY_LOCAL_MACHINE\SOFTWARE\Motive\M-Files\\Server\MFServer`. When the value is missing or invalid the server uses 7766. The server listens on all network interfaces on that port; open it in the firewall for remote clients. Other ports derive from it unless set explicitly: | Port | Default | Registry value | What | |---|---|---|---| | gRPC | 7766 | `gRPCEndpoint` | The API endpoint described on this page. | | mf-http-grpc gateway | gRPC + 1 (7767) | `GRPCWEBPORT` | `mf-http-grpc`, see [HTTP access](https://developer.m-files.com/APIs/gRPC-API/runtime/http-access/index.md). | | Local gRPC | - | - | For M-Files' own processes on the server machine. Do not use it. | The local endpoint picks a random free port when its default is taken and writes the port back to the registry, so its number is not stable. Some deployments put the M-Files reverse proxy (Traefik) in front of the server. The proxy then accepts gRPC over TLS on its HTTPS entry points, routes the `/MFiles` path prefix to the server and marks the calls as remote (see `mfiles-is-remote-call` in [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md)). Use the address and certificate of the proxy. ## TLS **M-Files Cloud** always uses TLS with a publicly trusted certificate. Use your gRPC library's default TLS credentials. **On-premises** the endpoint is plain HTTP/2 until an administrator enables TLS. TLS is enabled and its certificate installed with [SetServerEndpointSettings](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCServerAdmin/SetServerEndpointSettings/index.md) (`use_tls` and one certificate with its private key; the server supports a single certificate). The server stores the settings in its master database and restarts the endpoint. [GetServerEndpointSettings](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCServerAdmin/GetServerEndpointSettings/index.md) shows the current state. The server does not request client certificates. A plain endpoint carries passwords, tokens and session IDs unencrypted. Use it only on a trusted network or for local development. **Trusting the certificate.** The client must trust the CA that issued the server certificate, and the certificate's names must match the host name you connect to. M-Files' own Windows clients trust the Windows "Trusted Root Certification Authorities" store, so a certificate from an enterprise CA that is deployed there works for them without further setup. For a self-signed or private-CA certificate, give your gRPC client the CA certificate in PEM format: | Language | How to trust a PEM CA file | |---|---| | Python (`grpcio`) | `grpc.ssl_channel_credentials(root_certificates=pem_bytes)` | | Go | `credentials.NewClientTLSFromFile(caFile, "")` | | Node (`@grpc/grpc-js`) | `grpc.credentials.createSsl(fs.readFileSync(caFile))` | | C# (`Grpc.Net.Client`) | Install the CA in the machine's trust store, or validate the server certificate against it in `HttpClientHandler.ServerCertificateCustomValidationCallback` | The Python helper of the samples creates its channel like this (`MFILES_CA_CERT` points to the PEM file): ```python options = [("grpc.max_receive_message_length", MAX_RECEIVE_MESSAGE_BYTES), ("grpc.max_send_message_length", MAX_SEND_MESSAGE_BYTES)] if config.tls: root_certificates = Path(config.ca_cert).read_bytes() if config.ca_cert else None credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates) self._channel = grpc.secure_channel(config.address, credentials, options=options) else: self._channel = grpc.insecure_channel(config.address, options=options) self.channel = grpc.intercept_channel(self._channel, _MetadataInterceptor(self)) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* ## HTTP/2 Native gRPC needs HTTP/2 all the way to the server. With TLS, the client negotiates `h2` with ALPN, which gRPC libraries do by default. Without TLS, the server expects HTTP/2 with prior knowledge (h2c); gRPC libraries do this when you create an insecure channel. A proxy or load balancer between client and server must forward HTTP/2 and response trailers, because the gRPC status and the M-Files error details travel in trailers ([Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md)). ## Message size limits | Limit | Server default | Registry value (`...\Server\MFServer`) | |---|---|---| | Request message the server accepts | 48 MiB (50 331 648 bytes) | `gRPCMaxMessageLength` | | Response message the server sends | No limit (2 GiB - 1) | `gRPCMaxResponseLength` | | Metadata (headers or trailers) of a call | 512 KiB | `gRPCMaxMetadataSize` | A request over the server's limit fails with `RESOURCE_EXHAUSTED` and the message `SERVER: Received message larger than max (51380234 vs. 50331648)`. Most gRPC libraries accept responses of at most 4 MiB by default. M-Files responses can be larger, for example a 4 MiB file block plus its framing, or a large search result: such a call fails on the client with `RESOURCE_EXHAUSTED` and `CLIENT: Received message larger than max (4194309 vs. 4194304)`. Do what M-Files' own client does, and what the samples do: accept responses of any size, and send requests up to the server's limit (48 MiB unless the administrator changed it): | Language | No receive limit | Send limit | |---|---|---| | Python | channel option `grpc.max_receive_message_length` = -1 | `grpc.max_send_message_length` | | Go | `grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(math.MaxInt32))` | `grpc.MaxCallSendMsgSize(n)` | | C# (`Grpc.Net.Client`) | `GrpcChannelOptions.MaxReceiveMessageSize` = `null` | `MaxSendMessageSize` | | Node | channel option `grpc.max_receive_message_length` = -1 | `grpc.max_send_message_length` | Files larger than a single message are transferred in blocks; see [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md). ## HTTP/2 keepalive pings (not the session) The server closes connections whose client sends HTTP/2 keepalive pings more often than every 5 seconds while no data flows (registry value `gRPCMinPingIntervalMilliseconds`, default 5000). M-Files' own clients ping every 10 seconds, wait 5 seconds for the answer, and ping only while a call is active. If you enable keepalive in your client, use an interval of 10 seconds or more. HTTP/2 keepalive only keeps the connection open. The M-Files session expires on its own schedule unless you call [KeepAlive](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/KeepAlive/index.md); see [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#keeping-the-session-alive). ## Is the server reachable? [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md) needs no session and no metadata. It is the cheapest check that the address, TLS and HTTP/2 path work: ```text /MFiles.IRPCQueryVersion/QueryVersion request {} -> server_version: 7318397729964032 ``` `server_version` is a packed 64-bit version: major, minor, build and revision, 16 bits each, from the most significant end. `7318397729964032` is 26.11.16645.0: | Part | Bits | Value | |---|---|---| | major | `v >> 48` | 26 | | minor | `(v >> 32) & 0xFFFF` | 11 | | build | `(v >> 16) & 0xFFFF` | 16645 | | revision | `v & 0xFFFF` | 0 | ```csharp /// /// QueryVersion packs a version into 64 bits: major, minor, build and revision, 16 bits each. /// public static string DecodeVersion(ulong version) => $"{version >> 48}.{(version >> 32) & 0xFFFF}.{(version >> 16) & 0xFFFF}.{version & 0xFFFF}"; ``` *Source: [csharp/Recipes/Connect.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/Connect.cs)* [GetServerCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetServerCapabilities/index.md) is also anonymous and tells which optional features the server has; see [Versions and compatibility](https://developer.m-files.com/APIs/gRPC-API/runtime/versioning/index.md). If the check fails: | Status | Usual cause | |---|---| | `UNAVAILABLE` | Wrong host or port, firewall, an untrusted certificate, or a TLS client against a plain endpoint (`Tls handshake failed`). | | `UNIMPLEMENTED` | A plain (non-TLS) client against M-Files Cloud (`Received http2 header with status: 404`), a path that reached something other than M-Files Server, or a proxy that rewrote the path. | | `RESOURCE_EXHAUSTED` | A message size limit; see above. | --- # Cloud and on-premises Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/ The API, the messages and the session model are the same everywhere. What differs is how you reach the server and how users authenticate. | | M-Files Cloud | On-premises | |---|---|---| | Address | The vault host on port 443, for example `contoso.cloudvault.m-files.com:443` | The server's host name and the gRPC port, 7766 by default | | Transport | TLS, publicly trusted certificate | Plain HTTP/2 by default; TLS when the administrator enables it, with the certificate they install | | Front end | A front end between client and server; several server instances can serve one vault | The client talks to M-Files Server directly, or to the M-Files reverse proxy (Traefik) where one is deployed | | Stickiness | The front end sets the cookie `mfilesmsm`; send it back on later calls | None | | Typical authentication | Single sign-on through M-Files Login Service (plugin `MFLoginService.Default`), which M-Files Cloud vaults are moving to; otherwise through the vault's identity provider. Both are `AUTH_DATA_TYPE_PLUGIN` with a token | M-Files user name and password, Windows authentication, or single sign-on where configured | | Login accounts | Vault-level login accounts (`vault_login_accounts` capability) | Server-level login accounts unless configured otherwise | | Server version | Chosen by M-Files; check it with `QueryVersion` | Whatever version the customer runs | | mf-http-grpc gateway | Same host, port 443, paths `/grpc/v1/...` | Port 7767 by default | ## Practical consequences * **Find the authentication method first.** Call [GetAuthenticationConfiguration](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetAuthenticationConfiguration/index.md) anonymously with `host_name` set to the host you connect to. On an M-Files Cloud vault it returns `AUTH_METHOD_PLUGIN` and the OAuth plugin with its identity provider settings and the vault GUID. When the plugin is `MFLoginService.Default`, sign in through M-Files Login Service: see [M-Files Login Service](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#m-files-login-service). Other plugins: see [Single sign-on](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#single-sign-on-plugin-authentication). * **Keep cookies on M-Files Cloud.** Store `set-cookie` values from response headers and send them in a `cookie` header on every later call of the session. See [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness). * **Trust the right certificate on-premises.** A server with a private or self-signed certificate needs its CA in your client; a server without TLS needs an insecure channel. See [Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#tls). * **Do not hard-code capabilities.** Cloud and on-premises servers run different versions. Use [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md) and [GetServerCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetServerCapabilities/index.md); see [Versions and compatibility](https://developer.m-files.com/APIs/gRPC-API/runtime/versioning/index.md). ## The same first call everywhere Both deployments answer [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md) without a session or any metadata: | Target | Channel | Example answer | |---|---|---| | `contoso.cloudvault.m-files.com:443` | TLS, default trust | `server_version` 7318389121155072 (26.9.16357.0) | | `mfserver.example.local:7766` | Insecure (or TLS with your CA) | `server_version` 7318397729964032 (26.11.16645.0) | The [Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#is-the-server-reachable) page explains how to decode the version and what a failure means. --- # Authentication and sessions Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/ Almost every call needs a session. [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) authenticates the caller and returns a session ID; no request message has a field for it. It travels in the binary call metadata `mfiles-session-id-bin` of every later call. ```text LogIn -> session_data.session_id (48 bytes) every call -> metadata mfiles-session-id-bin: ``` gRPC libraries base64-encode `-bin` metadata values themselves: pass the raw bytes. The value must be exactly the 48 bytes from `session_data.session_id`; other lengths are rejected. A session belongs to one vault, or to no vault (a server-level session). ## Calls that need no session [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md), [GetServerCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetServerCapabilities/index.md), [GetAuthenticationConfiguration](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetAuthenticationConfiguration/index.md), [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) and a few public methods (for example those of `IRPCSharedLinksPublic`, which a shared link's access key authorizes) are served without a session. The reference marks each method with its session requirement. ## Choosing the authentication method | `authentication_data.type` | Use for | Round trips | |---|---|---| | `AUTH_DATA_TYPE_CREDENTIALS` | User name and password: M-Files (`CREDENTIALS_TYPE_MFILES`) or Windows (`CREDENTIALS_TYPE_WINDOWS`) accounts | 1 | | `AUTH_DATA_TYPE_WINDOWS` | The current Windows user, single sign-on with SSPI | 2 or more | | `AUTH_DATA_TYPE_PLUGIN` | An authentication plugin, for example OAuth / OpenID Connect single sign-on with a token | 1 or more | | `AUTH_DATA_TYPE_ANONYMOUS` | Anonymous access, where a vault allows it | 1 | | `AUTH_DATA_TYPE_MFILES_LEG1`, `_LEG2` | Challenge-response for M-Files accounts; the password itself is not sent. Not covered here | 2 | To learn what a server or vault expects, call [GetAuthenticationConfiguration](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetAuthenticationConfiguration/index.md) anonymously. `AUTH_METHOD_PLUGIN` with plugin information means single sign-on through that plugin; `AUTH_METHOD_UNSPECIFIED` with no plugins means the server has no plugin configured for the target and you use credentials or Windows authentication. Credentials and tokens travel in the request. Send them only over TLS ([Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#tls)). ## The login loop Every type uses the same loop. Single-step types finish in the first call. 1. Call `LogIn` with `environment_data`, `client_data`, `login_data` and `authentication_data`. 2. If `response.session_data.session_id` is set, the login is complete. 3. Otherwise the server needs another step: send `LogIn` again with the next authentication data, and copy `response.authentication_data.authentication_attempt_identifier` into the `authentication_attempt_identifier` field of the union member you send (for example `data.windows.authentication_attempt_identifier`). Repeat from 2. The server's half of the exchange is in `response.authentication_data.data`. For Windows and plugin authentication it carries the next token or values and `is_done`. A failed authentication is reported as `E_MFILES_AUTHENTICATION_FAILED` ("Authentication failed.") without details: a wrong password, an unknown user and a login blocked by the server's login attack protection look the same. Repeated failures make the protection block further attempts for a while, so do not retry a failing login in a loop. ### User name and password One call. Fill `authentication_data.data.credentials` (`username`, `password`, `type`, and `domain` for Windows accounts) and set `authentication_data.type` to `AUTH_DATA_TYPE_CREDENTIALS`: ```python authentication = structs_pb2.AuthDataClient( type=enums_pb2.AUTH_DATA_TYPE_CREDENTIALS, data=structs_pb2.AuthDataClientUnion(credentials=structs_pb2.Credentials( type=enums_pb2.CREDENTIALS_TYPE_MFILES, username=username, password=password))) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* ```python request = IRPCLogin_pb2.LogInRequest( environment_data=structs_pb2.EnvironmentData( client_name=self.client_name, host_platform="Python"), client_data=self._client_data(), login_data=structs_pb2.LoginData( server_hostname=self.config.host, vault_guid=self.config.vault), authentication_data=authentication) response = self.login.LogIn(request) if not response.session_data.session_id: raise RuntimeError("LogIn did not complete: the authentication needs more steps") # From now on, the interceptor sends this in "mfiles-session-id-bin" on every call. self.session_id = response.session_data.session_id ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* ### Windows authentication (SSPI) The client runs the Windows SSPI handshake and relays its tokens through `LogIn`. The server accepts tokens of the Negotiate package (Kerberos or NTLM); M-Files' C# client library uses NTLM. The loop: 1. Initialize an SSPI client context; it produces the first token. 2. `LogIn` with `type: AUTH_DATA_TYPE_WINDOWS` and `data.windows.auth_data` = the token. Leave `authentication_attempt_identifier` unset in the first call. 3. The response has no session yet. Feed `response.authentication_data.data.windows.auth_data` to SSPI to get the next token, and send it with `data.windows.authentication_attempt_identifier` = the attempt identifier from the response. 4. Stop when the response has `session_data.session_id` (and `data.windows.is_done` is true). With NTLM and Negotiate against a local server, the login completed in two `LogIn` calls. If the Windows user has no account in the vault, the last call fails with `E_MFILES_ACCESS_DENIED` ("You do not have a user account in this document vault"). ### Single sign-on (plugin authentication) The server does not run the identity provider's sign-in for you. Your client obtains a token from the identity provider and hands it to `LogIn`: 1. **Discover**, anonymously: [GetAuthenticationConfiguration](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetAuthenticationConfiguration/index.md) with `host_name` set to the host you connect to (or with `vault_guid`), and `type` set to `ACCOUNT_TYPE_WINDOWS`: users who sign in through an identity provider have Windows-type login accounts, and a vault can limit its configuration to that account type (configuration scope `*:WINDOWS:`). Such a vault returns no plugin when `type` is left out. The response names the plugin (`plugin_infos[].name`, for example `MFLoginService.Default`, assembly `MFiles.AuthenticationProviders.OAuth`). Its `plugin_configuration` holds the identity provider settings (`AuthorizationEndpoint`, `TokenEndpoint`, `ClientID`, `Scope`, `RedirectURI`, `UsePkceWithAuthorizationCode`, `UseAccessTokenInWeb`, ...) and its `system_configuration` holds `Scope` (the configuration scope) and `VaultGUID`. 2. **Sign in** at the identity provider with those settings (for OAuth: authorization code flow, with PKCE when `UsePkceWithAuthorizationCode` says so) and get the token. 3. **Log in** with `type: AUTH_DATA_TYPE_PLUGIN` and `data.plugin`: | Field | Value | |---|---| | `plugin_name` | `plugin_infos[].name` | | `configuration_scope` | `system_configuration` value `Scope` | | `vault_guid` | `system_configuration` value `VaultGUID`, when present | | `authentication_attempt_identifier.data` | 32 zero bytes in the first call | | `data_format` | `PLUGIN_AUTH_DATA_FORMAT_UNENCRYPTED` | | `auth_data` | One named value: key `Token`, a `DATATYPE_TEXT` typed value holding the token | ```python plugin_data = structs_pb2.AuthDataClientUnion.PluginData( plugin_name=plugin, configuration_scope=scope, vault_guid=plugin_vault, authentication_attempt_identifier=structs_pb2.AuthenticationAttemptIdentifier( data=bytes(32)), data_format=enums_pb2.PLUGIN_AUTH_DATA_FORMAT_UNENCRYPTED, auth_data=[structs_pb2.NamedValue(key="Token", value=structs_pb2.TypedValue( type=enums_pb2.DATATYPE_TEXT, data=structs_pb2.TypedValueUnion(text=token)))]) authentication = structs_pb2.AuthDataClient( type=enums_pb2.AUTH_DATA_TYPE_PLUGIN, data=structs_pb2.AuthDataClientUnion(plugin=plugin_data)) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* A plugin can ask for more steps; the response then has `data.plugin.is_done` false and no session. Continue with the loop above. The session you get is the same kind as from a password login. ### M-Files Login Service M-Files Login Service is M-Files' own OpenID Connect sign-in service. It signs the user in with the identity provider of their organization (for example Microsoft Entra ID) and issues tokens for the vault. A vault that uses it returns the plugin `MFLoginService.Default` (assembly `MFiles.AuthenticationProviders.OAuth`) from [GetAuthenticationConfiguration](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetAuthenticationConfiguration/index.md). When a vault offers it, use it: your application then signs users in the same way as M-Files' own clients. The plugin's `plugin_configuration` has everything the sign-in needs. Read it from the vault every time; do not hard-code it, because the endpoints differ between environments and the client ID and scope belong to the vault: | Setting | Example | Use | |---|---|---| | `AuthorizationEndpoint` | `https:///connect/authorize` | Where the browser signs in | | `TokenEndpoint` | `https:///connect/token` | Where you exchange the code and refresh tokens | | `ClientID` | `mfclient,` | `client_id`. A public client: there is no client secret | | `Scope` | `openid mfiles:// offline_access` | `scope`. `offline_access` gets you a refresh token | | `RedirectURI` | `http://localhost/signin-oidc` | Its path is the path of your redirect URI (see below) | | `UsePkceWithAuthorizationCode` | `true` | Use PKCE (`S256`) | | `UseIdTokenAsAccessToken` | `false` | Send the access token, not the ID token, to `LogIn` | | `LogoutEndpoint` | `https:///connect/endsession` | To sign the user out of the Login Service | `system_configuration` has `Scope` (for example `*:` or `*:WINDOWS:`) and `VaultGUID`, which go into `LogIn`. ```python def find_login_service(client: MFilesClient) -> tuple[structs_pb2.PluginInfo, dict, dict]: """The vault's Login Service plugin, its OAuth settings and its system configuration.""" # Users who sign in through an identity provider have Windows-type login accounts. A vault can # limit its configuration to that account type, and then answers only when the type is given. response = client.login.GetAuthenticationConfiguration( IRPCLogin_pb2.GetAuthenticationConfigurationRequest( host_name=client.config.host, type=enums_pb2.ACCOUNT_TYPE_WINDOWS)) for plugin in response.plugin_infos: if plugin.name.startswith(LOGIN_SERVICE_PREFIX): settings = {v.key: v.value.data.text for v in plugin.plugin_configuration.value} system = {v.key: v.value.data.text for v in plugin.system_configuration.value} return plugin, settings, system raise SystemExit("The vault does not offer M-Files Login Service; " f"plugins: {[p.name for p in response.plugin_infos]}") ``` *Source: [python/recipes/login_service.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/login_service.py)* **Sign in.** Run the OAuth 2.0 authorization code flow with PKCE in the user's browser. A desktop or command-line program receives the code on a loopback address: listen on `http://127.0.0.1:` with the path of `RedirectURI`, and send that as `redirect_uri`. The Login Service accepts any port with the loopback IP address (as RFC 8252 describes for native apps), but not with the name `localhost`: an authorization request with `http://localhost:/...` ends on the Login Service's error page. ```python def sign_in(settings: dict) -> dict: """Runs the authorization code flow with PKCE in the browser; returns the token response.""" verifier = secrets.token_urlsafe(48) challenge = base64.urlsafe_b64encode( hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode() state = secrets.token_urlsafe(16) received: dict = {} class Callback(http.server.BaseHTTPRequestHandler): def do_GET(self): received.update(urllib.parse.parse_qs(urllib.parse.urlsplit(self.path).query)) self.send_response(200) self.send_header("Content-Type", "text/plain; charset=utf-8") self.end_headers() self.wfile.write(b"Signed in. You can close this window.") def log_message(self, *args): pass # A loopback IP address with any free port: the Login Service accepts it as the redirect # URI of a native client (RFC 8252). Keep the path of the configured RedirectURI. path = urllib.parse.urlsplit(settings["RedirectURI"]).path or "/" with http.server.HTTPServer(("127.0.0.1", 0), Callback) as server: redirect_uri = f"http://127.0.0.1:{server.server_port}{path}" webbrowser.open(settings["AuthorizationEndpoint"] + "?" + urllib.parse.urlencode({ "client_id": settings["ClientID"], "response_type": "code", "scope": settings["Scope"], "redirect_uri": redirect_uri, "state": state, "code_challenge": challenge, "code_challenge_method": "S256", })) print("Sign in in the browser window that opened...") server.timeout = 300 server.handle_request() # Waits for the browser to come back with the code. if received.get("state") != [state] or "code" not in received: raise SystemExit(f"Sign-in failed: {received.get('error', ['no code'])[0]}") # Exchange the code for tokens. A native client has no secret: PKCE proves that it started # the flow. body = urllib.parse.urlencode({ "grant_type": "authorization_code", "code": received["code"][0], "redirect_uri": redirect_uri, "client_id": settings["ClientID"], "code_verifier": verifier, }).encode() with urllib.request.urlopen(urllib.request.Request(settings["TokenEndpoint"], body)) as reply: return json.load(reply) ``` *Source: [python/recipes/login_service.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/login_service.py)* **Log in** with the access token, as described for plugin authentication above, with the plugin's name, its configuration scope and vault: ```python # The plugin validates the access token unless the configuration says to use the ID # token instead. use_id_token = settings.get("UseIdTokenAsAccessToken", "false").lower() == "true" token_name = "id_token" if use_id_token else "access_token" client.config.vault = client.config.vault or system["VaultGUID"] session = client.log_in_with_token(tokens[token_name], plugin.name, system["Scope"], system.get("VaultGUID", "")) ``` *Source: [python/recipes/login_service.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/login_service.py)* **Log in again later** without the browser: when the session has expired, get new tokens with the refresh token (`grant_type=refresh_token`, the refresh token and `client_id`; no secret and no PKCE) and call `LogIn` with the new access token. ```python def refresh(settings: dict, refresh_token: str) -> dict: """New tokens for a refresh token (the scope "offline_access" asks for one). The reply has a new refresh token, and the one passed in stops working: store the new one before using the new access token, and let only one process refresh at a time. """ body = urllib.parse.urlencode({ "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": settings["ClientID"], }).encode() with urllib.request.urlopen(urllib.request.Request(settings["TokenEndpoint"], body)) as reply: return json.load(reply) ``` *Source: [python/recipes/login_service.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/login_service.py)* The whole program is `recipes/login_service.py` in the Python samples. ### Programs that run without a person A service or scheduled job cannot open a browser. With M-Files Login Service it signs in once interactively and then renews its tokens with the refresh token: * Read the access token's lifetime from `expires_in` in each token response; do not assume a fixed value. `LogIn` accepts a refreshed access token like the first one. * A refresh can return a new refresh token that replaces the one used. A replaced refresh token fails with HTTP 400 `invalid_grant`; the newest one keeps working. So a program that renews logins unattended must: * **Store the new refresh token first**, durably and atomically, before it uses the new access token. If the new token is lost, only an interactive sign-in can start the chain again. A refresh token signs in as the user without a browser, so keep it out of files and logs: a desktop tool can use the operating system's credential store (Windows Credential Manager, the macOS keychain; in Python, the `keyring` package). Store only the refresh token there and get a new access token at start-up; Windows Credential Manager holds at most 2,560 bytes per entry. * **Let one process refresh.** Two processes that refresh with the same token race: one of them gets `invalid_grant`. Use a lock, or one process that hands out access tokens. * **Renew before the access token expires**, and log in again with the fresh token when the session ends ([UNAUTHENTICATED](#when-a-call-answers-unauthenticated)). The refresh token's own lifetime, and how conditional-access policies of the user's organization affect it, are set by the Login Service and the identity provider; plan for an occasional interactive sign-in. ### Anonymous `type: AUTH_DATA_TYPE_ANONYMOUS` with an empty `data.anonymous`. It succeeds only for vaults that allow anonymous access; otherwise it fails with `E_MFILES_AUTHENTICATION_FAILED`. ## Binding the session to a vault `login_data.vault_guid` decides what the session is for: | `vault_guid` | Result | |---|---| | The vault's GUID, `{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}` | A vault session: use it for vault calls. | | `{00000000-0000-0000-0000-000000000000}` | A server-level session: server calls such as [GetVaults](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVaults/GetVaults/index.md) only. Vault calls fail with `E_MFILES_LOGIN_REQUIRED` ("Login required."). | | Empty string | Fails with `E_MFILES_INVALID_VAULT_GUID_X_DESC` (`Invalid Vault GUID: ""`). | | Unknown GUID | Fails with `E_MFILES_UNKNOWN_VAULT`. | Set `login_data.server_hostname` to the host name you connected to. On servers with vault-level login accounts (M-Files Cloud), a server-level login uses it to find the vault whose accounts apply. **Two ways to reach a vault:** * **`LogIn` with the vault GUID.** One authentication, one session, one round trip. This is the simplest path for a program that works with one known vault, and what M-Files' C# client library does. * **Server-level `LogIn`, then [LogInWithSession](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogInWithSession/index.md).** `LogInWithSession` takes an existing session in `mfiles-session-id-bin` and a `login_data` with the vault GUID, and returns a **new** session for that vault without authenticating again. The original session stays valid; log out both. Use this when you first need to list the vaults, or want sessions to several vaults from one sign-in (for example one SSO token). M-Files' web client logs in this way. Both were verified against a server: the vault session from `LogInWithSession` has its own 48-byte ID, and logging it out leaves the server-level session alive. ## Keeping the session alive `session_data.keep_alive_interval_in_seconds` (450 seconds on a server with default settings) is how often to call [KeepAlive](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/KeepAlive/index.md). The server drops a session after its session timeout without a `KeepAlive` call: 15 minutes by default (twice the interval). A vault session can ask for a longer timeout, at most 7 days, with `login_data.client_session_timeout_in_seconds`; a shorter or longer value is ignored. **Other calls do not extend the session**: a program that is busy making calls must still call `KeepAlive` on a timer. Upload sessions have their own interval, `keep_upload_session_alive_interval_in_seconds`; see [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md). ## When a call answers UNAUTHENTICATED `UNAUTHENTICATED` means the server does not accept the session: it was never valid, expired, was logged out, the `mfiles-session-id-bin` header is missing, or it appears more than once. The status carries a fixed message ("The current session with M-Files Server is no longer valid...") and no error trailers. Log in again with `LogIn` and continue with the new `session_id`. ## Logging out [LogOut](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogOut/index.md) with the session in `mfiles-session-id-bin` ends the session and releases the license it holds; a session that is not logged out holds its license until it times out. Every later call with that session ID, including `KeepAlive` and a second `LogOut`, answers `UNAUTHENTICATED`. ## Session assertions Some methods also accept a session assertion in the metadata `x-mf-session-assertion` instead of `mfiles-session-id-bin` (`SESSION_REQUIREMENT_SESSION_OR_ASSERTION` in the reference). Session assertions are for M-Files' own services; applications use the session ID. --- # Call metadata Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/ Part of the M-Files call contract is not in any message: it travels in gRPC metadata (HTTP/2 headers and trailers). In short: send `mfiles-session-id-bin` (the session ID from `LogIn`), `mfiles-activity-id-bin` (16 random bytes) and `mfiles-is-remote-call: true` on every call, keep cookies on M-Files Cloud, and read the error and metadata structure version trailers from the responses. This page lists every key. ## Binary keys Keys that end in `-bin` carry bytes. gRPC libraries base64-encode them on the wire and decode them for you, so pass and read raw bytes: | Language | Sending `mfiles-session-id-bin` | |---|---| | Python | `metadata=[("mfiles-session-id-bin", session_id_bytes)]` | | Go | `metadata.Pairs("mfiles-session-id-bin", string(sessionID))` | | C# | `new Metadata { { "mfiles-session-id-bin", sessionIdBytes } }` | The server reads a key only when it occurs exactly once in the call. A key sent twice counts as missing. ## Request metadata | Key | Value | When to send | |---|---|---| | `mfiles-session-id-bin` | The 48 bytes of `session_data.session_id` from [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md), unchanged | Every call that needs a session. See [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). | | `mfiles-activity-id-bin` | 16 bytes, a new random GUID per call | Recommended on every call. | | `mfiles-is-remote-call` | `true` | Always. | | `cookie` | The cookies the server set, `name=value; name2=value2` | Every call, once the server has set any. See below. | | `x-mf-session-assertion` | A session assertion | Not for third-party callers; see [Authentication](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#session-assertions). | **`mfiles-session-id-bin`.** Missing, unknown or expired: `UNAUTHENTICATED`. A value that is not 48 bytes long is rejected: on a plain endpoint with `UNKNOWN` and `E_INVALIDARG` ("The parameter is incorrect."), on a TLS endpoint with `UNAUTHENTICATED`. Methods that need no session ignore it. **`mfiles-activity-id-bin`.** Identifies the call in the server's logs and traces, so support can find it. The server reads it only from calls that carry a session ID. Optional, but if you send it, it must be exactly 16 bytes: other lengths fail with `E_INVALIDARG`. M-Files' clients send a new GUID for every call; log it on your side to correlate. **`mfiles-is-remote-call`.** Tells the server that the caller is not one of its own local processes. Send `true` on every call; the server checks only that the key is present. **Not read.** MFServer reads no other request metadata. In particular, W3C trace context (`traceparent`) is not used; correlate with `mfiles-activity-id-bin`. ## Response metadata | Key | Where | Value | |---|---|---| | `mfiles-metadata-version-bin` | Trailer, successful calls of methods that report it | The vault's metadata structure version: 4 bytes, little-endian signed 32-bit integer | | `mfiles-error-text-bin` | Trailer, failed calls | UTF-8 text of the server-side error stack. See [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md). | | `mfiles-error-serialized-bin` | Trailer, failed calls | A serialized [RPCErrorInfoArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.RPCErrorInfoArray/index.md). See [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md). | | `set-cookie` | Header, M-Files Cloud | For example `mfilesmsm=69621a771a22b419; Path=/; Max-Age=172800; Secure; SameSite=None` | **`mfiles-metadata-version-bin`.** Methods marked `returns_metadata_structure_version` in the reference (for example [GetObjectTypes](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectTypes/index.md)) return it on success. The bytes `00 04 00 00` are version 1024. The version changes when the vault's metadata structure (object types, value list definitions, classes, property definitions and so on) changes; it does not change when objects or value list items change. A failed call has no such trailer. [Metadata structure version and caching](https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/index.md) lists which results are safe to cache by this version. An on-premises server sends no response headers of its own; everything else is in trailers. ## Cookies and stickiness M-Files Cloud puts a front end in front of the server, and a vault can be served by more than one server instance. The front end sets the cookie `mfilesmsm` on its responses to pin a client to one instance: * A new connection without the cookie gets one; different connections can get different values. * A call that sends a valid `mfilesmsm` back gets no new `set-cookie`. * An unknown value is replaced with a new cookie. Keep cookies like a browser: store `set-cookie` values from response headers and send them in a `cookie` header on every later call of the same session. M-Files' own clients do this for every call. Multi-step file transfers depend on it, because their upload and download sessions exist only in the server process that created them (see [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md)). The cookie lasts 48 hours (`Max-Age=172800`). gRPC libraries do not keep cookies themselves; add an interceptor that reads the response headers and adds the `cookie` header. ## Deadlines and cancellation Set a deadline (timeout) on every call; without one, a call to a stalled connection waits forever. The server does not stop a unary call when its deadline passes or the client cancels it: the call runs to completion and only its result is lost. Verified: an `AddObjectWithFiles` call with a 5 ms deadline returned `DEADLINE_EXCEEDED` to the client, and the object was created anyway. Therefore: * After `DEADLINE_EXCEEDED` or `CANCELLED` on a call that changes data, the outcome is unknown. Read the current state before you retry. * Methods marked `idempotency_level = IDEMPOTENT` in the `.proto` can be retried as they are. ## Call importance Some request messages have a [CallImportance](https://developer.m-files.com/APIs/gRPC-API/Reference/Enums/MFiles.CallImportance/index.md) field (`callimp` or `call_importance`). Leave it at `CALL_IMPORTANCE_DEFAULT` or set `CALL_IMPORTANCE_NORMAL`: such calls are never rejected for load. With `CALL_IMPORTANCE_CAN_BE_REJECTED` the server refuses the call with `E_MFILES_CALL_REJECTED` ("The server is too busy to process the request at this time.") while it is serving more requests than its limit (by default the number of logical processors of the server machine). Use it for background work such as prefetching, and try again later when it is rejected. --- # Errors Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/errors/ M-Files Server reports every failure of its own with one of two gRPC status codes: | Status | Meaning | Details | |---|---|---| | `UNAUTHENTICATED` | The session is missing, unknown, expired or logged out. | A fixed message ("The current session with M-Files Server is no longer valid..."). No error trailers. Log in again; see [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#when-a-call-answers-unauthenticated). | | `UNKNOWN` | Any other M-Files error, including a failed login. | The status message is the top-level error text. The trailers carry the full M-Files error. | Other status codes come from the gRPC layer, not from M-Files logic: | Status | Typical cause | |---|---| | `RESOURCE_EXHAUSTED` | A message over a size limit (`SERVER: Received message larger than max` or `CLIENT: ...`). See [Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#message-size-limits). | | `UNIMPLEMENTED` | The method does not exist on this server, for example a method newer than the server version. | | `UNAVAILABLE` | No connection: address, port, firewall or TLS problem. | | `DEADLINE_EXCEEDED`, `CANCELLED` | Set by the client. The server may still complete the call; see [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#deadlines-and-cancellation). | ## The error trailers A call that fails with `UNKNOWN` has two trailers: | Trailer | Content | |---|---| | `mfiles-error-serialized-bin` | A serialized [RPCErrorInfoArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.RPCErrorInfoArray/index.md): the M-Files error stack. | | `mfiles-error-text-bin` | The same stack as UTF-8 text, one line per entry. For logs. | Each [RPCErrorInfo](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.RPCErrorInfo/index.md) has `error_code` (an HRESULT as a signed 32-bit integer), `description` (extra detail, often empty), and `file` and `line` (the server source location). The entries run from the original cause (first) to the top-level error (last); the status message describes the last one. The same code usually repeats as the error passes up through the server. Example for an unknown vault GUID at login: | Index | `error_code` | Meaning | `description` | |---|---|---|---| | 0 | `0x80040120` | Free-text detail | `Vault ID: {11111111-2222-3333-4444-555555555555}` | | 1, 2 | `0x80040020` | `E_MFILES_UNKNOWN_VAULT` | | Status message: `The specified document vault does not exist.` followed by the Vault ID line. When the stack is too large for the trailer limit, the server drops entries from the middle and shortens long texts; the first and the last entries are kept. **Which code to act on.** Test whether a code you handle appears anywhere in the stack. The last entry is the top-level error and the most stable choice; the first one names the original cause, which is sometimes more specific (for "access denied", the first entry says whether vault-level or object-level rights were missing) and sometimes only a free-text carrier (`0x80040120`). Do not parse the message text: it can change between versions. A missing permission does not always produce an error: [Permissions](https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/index.md#what-access-denied-looks-like) shows when the server answers "Not found." or leaves items out instead. The text trailer and `file`/`line` are diagnostics that include server source file names and the server version. Log them; do not show them to end users. ### Decoding in Python The samples' helper decodes the stack into a list of entries and their unsigned codes: ```python self.long_message: str = trailers.get(ERROR_TEXT_KEY, b"").decode("utf-8", "replace") self.stack = list(structs_pb2.RPCErrorInfoArray.FromString( trailers.get(ERROR_STACK_KEY, b"")).value) self.codes = [entry.error_code & 0xFFFFFFFF for entry in self.stack] # cause first # The top-level error, skipping entries that only pass the error on. self.hresult: Optional[int] = next( (code for code in reversed(self.codes) if code != E_PASS_THROUGH), self.codes[-1] if self.codes else None) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* A code is often written as unsigned hex (`0x800407E4`) and sometimes as a signed integer (`-2147219484`); `code & 0xFFFFFFFF` converts the signed value to the unsigned one. The [error handling guide](https://developer.m-files.com/APIs/gRPC-API/guides/errors/index.md) shows the same in C# and Go. ## Common error codes Observed against a server unless marked "code only". The message is the English status message. | Name | HRESULT | Signed | Message | When | What to do | |---|---|---|---|---|---| | Invalid session | `0x8004001B` | -2147221477 | The current session with M-Files Server is no longer valid... | Session missing, expired or logged out. Status `UNAUTHENTICATED`, no trailers. | Log in again with `LogIn`. | | `E_MFILES_AUTHENTICATION_FAILED` | `0x8004001A` | -2147221478 | Authentication failed. | Wrong password, unknown user, anonymous login not allowed, login temporarily blocked. | Ask for new credentials; do not retry in a loop. | | `E_MFILES_LOGIN_REQUIRED` | `0x80040043` | -2147221437 | Login required. | A vault method called with a server-level session. | Log in to the vault (`LogInWithSession`). | | `E_MFILES_UNKNOWN_VAULT` | `0x80040020` | -2147221472 | The specified document vault does not exist. | Login with a vault GUID the server does not have. | Check the GUID; list vaults with `GetVaults`. | | `E_MFILES_INVALID_VAULT_GUID_X_DESC` | `0x80040034` | -2147221452 | Invalid Vault GUID: "..." | `vault_guid` is empty or not a GUID. | Send a GUID; all zeros for server level. | | `E_MFILES_VAULT_OFFLINE` | `0x80040061` | -2147221407 | (code only) | The vault is offline for maintenance. | Retry later. | | `E_MFILES_ACCESS_DENIED` | `0x800407E4` | -2147219484 | Access denied. You do not have the access rights required by this operation. | An operation the user's rights do not allow, or an object the user cannot see. | Report to the user; do not retry. | | `E_MFILES_LACKING_VAULT_LEVEL_ACCESS_RIGHTS` | `0x800400A9` | -2147221335 | (first entry under access denied) | The user lacks a vault-level right, for example for administrative methods. | As access denied. | | `E_MFILES_LACKING_DOC_LEVEL_ACCESS_RIGHTS` | `0x800400AA` | -2147221334 | Access denied. You do not have sufficient access rights to the object. (first entry) | The object's permissions do not allow the operation. | As access denied. | | `E_MFILES_NOT_FOUND` | `0x8004000B` | -2147221493 | Not found. | An object ID that does not exist or was destroyed; a download ID that is not open. | Check the ID; for downloads, start again. | | `E_MFILES_NOT_FOUND_X` | `0x800408A4` | -2147219292 | Not found. 9999 | An unknown item ID, such as an object type ID; the description names it. | Check the ID. | | `E_MFILES_DOCUMENT_REMOVED` | `0x8004006D` | -2147221395 | The object has been removed. (ID: 529) | Changing a deleted object. | Undelete it first, or skip it. | | `E_MFILES_CHECKED_OUT_TO_ANOTHER_USER` | `0x80040019` | -2147221479 | The object is checked out to another user. | Checking out or changing an object someone else has checked out. | Retry later or ask the user to check it in. | | `E_MFILES_NOT_CHECKED_OUT` | `0x800400B2` | -2147221326 | The object is not checked out. | Changing a checked-in object without checking it out, and without the method's option to modify a checked-in object. | Check out first, or use that option. | | `E_MFILES_NEWER_VERSION_EXISTS` | `0x800400BB` | -2147221317 | There is a newer version of this object on the server. | A change based on an old version, with the request's "fail if a newer version exists" option set. | Read the latest version, merge, try again. | | `E_MFILES_ATTEMPT_TO_CHANGE_CLASS` | `0x80040015` | -2147221483 | You cannot change the class of the object using this method. | The class property (100) sent to a method that does not change the class; observed with `SetProperties` even when the value was the object's current class. | Leave the class out. | | `E_MFILES_WRONG_DATATYPE_FOR_PROPERTYDEF_X_DESC` | `0x8004009A` | -2147221350 | Wrong data type for property definition "Name or title". | A typed value whose type does not match the property definition. | Use the property definition's data type. | | `E_MFILES_UNABLE_TO_ACCESS_PROPERTYDEF` | `0x8004097E` | -2147219074 | You can't get access to the property "987654". Make sure that the property definition exists and you have access to it. | A property definition ID that does not exist or is hidden from the user. | Check the ID. | | `E_INVALIDARG` | `0x80070057` | -2147024809 | The parameter is incorrect. | A malformed request: a wrong-length binary header, a "latest version" reference where a specific version is required, a download block over 4 MiB, and many more. Look for a description in the stack. | Fix the request. | | `E_NOTIMPL` | `0x80004001` | -2147467263 | Not implemented. | A published method whose feature is not enabled on this server, such as the stateless upload methods, or a method that serves only the HTTP gateway, such as `DownloadFile`. | Use the equivalent the method's reference names; for files see [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md). | | `E_MFILES_CALL_REJECTED` | `0x80040044` | -2147221436 | The server is too busy to process the request at this time. (code only) | A call sent with `CALL_IMPORTANCE_CAN_BE_REJECTED` while the server is busy. | Retry later. | --- # Files Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/files/ File content does not travel in ordinary object messages. You download it in blocks through a download session, and you upload it either inline in the request that creates the object (up to 10 MiB) or in blocks through an upload session. Files belong to object versions; see [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md#files-single-file-and-multi-file-documents). ## Which method to use | Task | Use | |---|---| | Read a file of an object version | [DownloadFile_Begin](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Begin/index.md), [DownloadFile_ReadBlock](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_ReadBlock/index.md) in a loop, [DownloadFile_Stop](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Stop/index.md) | | Create a single-file document with a file of at most 10 MiB | [AddObjectWithFiles](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObjectWithFiles/index.md) with the content inline (`OBJ_FILE_SOURCE_TYPE_FILE_CONTENT`) | | Create a document with a file of any size | [UploadTempFile](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFile/index.md) in blocks, [UploadTempFileCommit](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFileCommit/index.md), then `AddObjectWithFiles` with the upload ID (`OBJ_FILE_SOURCE_TYPE_UPLOADED_FILE`) | `DownloadFile`, `DownloadFileWithDownloadID`, `DownloadFileWithDownloadKey`, `GetFileDownloadUrl` and `GetFileDownloadUrl2` of `IRPCObjectOperations` serve the mf-http-grpc gateway: over native gRPC they answer "Not implemented.". The stateless upload services are described at the end of this page. The [download guide](https://developer.m-files.com/APIs/gRPC-API/guides/download-file/index.md) and the [create-document guide](https://developer.m-files.com/APIs/gRPC-API/guides/create-document/index.md) show the calls in code. ## Downloading 1. Take the file's version from the object version: `ObjectVersion.files[]` gives each file's `file_ver` (file ID and file version), `title`, `extension` and `size`. 2. Call `DownloadFile_Begin` with `obj_file_ver` (the object's `obj_id` and the file's `file_ver`), `format` (`FILE_FORMAT_NATIVE` for the file as stored), and **`prepare_file_for_downloading: true`**. Without that flag the server only checks access and records the access, and opens no download session. Set `need_checksum: true` to get the MD5 checksum in `file_outline.md5_checksum`. The response has `download_id` and `file_outline` with the `size` to expect. 3. Call `DownloadFile_ReadBlock` with `download_id`, `offset` from 0 and `block_size` (request `min(block size, size - offset)` each time) until you have read `file_outline.size` bytes. Do not read again after the last byte: the server has closed the download session. `block_size` can be at most 4 MiB (4 194 304 bytes); a larger value fails with `E_INVALIDARG` ("Block size exceeded the limit of 4194304 bytes."). 4. Call `DownloadFile_Stop` with the `download_id`. Verified with a 3 MiB file in 4 MiB blocks: the downloaded bytes and the server's MD5 matched the upload. **Your receive limit.** A 4 MiB block plus framing is just over the 4 MiB default receive limit of most gRPC libraries and fails on the client with `RESOURCE_EXHAUSTED`. Raise the limit, or read smaller blocks such as 1 MiB. See [Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#message-size-limits). **Download sessions.** * A download ID is valid only in the session that opened it, and only on the server process that opened it. On M-Files Cloud send the stickiness cookie ([Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness)). From another session the ID answers `E_MFILES_NOT_FOUND` ("The specified download session 1 does not exist."). * When you read the file from start to end, the server closes the session as soon as you read the last block; a later `ReadBlock` answers that the session does not exist. `DownloadFile_Stop` still succeeds, so always call it: it releases the session when you stop early or read out of order. * The server closes download sessions that are left open: the timeout is 10 minutes by default. ## Uploading inline For small files, put the content in the creating request: ```text AddObjectWithFiles object_type_id: 0 properties: [ class, name or title, ... ] check_in: true files: { type: OBJ_FILE_SOURCE_TYPE_FILE_CONTENT data.file_content: { title: "report", extension: "pdf", file_data: } } ``` Inline content works only for a single-file document (object type 0 with property 22, Single file, true), needs a `title`, and is limited to 10 MiB (10 485 760 bytes); a larger file fails with `E_INVALIDARG` ("The parameter is incorrect. (Inline file content: fileSize exceeds the maximum allowed for inline content.)"), and so does inline content for a multi-file document. A 5 MiB file was created this way. Use an upload session for anything else. ## Uploading in blocks (upload sessions) 1. Call `UploadTempFile` with `upload_id: 0`, `total_size_in_bytes`, `offset: 0` and the first block in `data`. Keep the returned `upload_id`. 2. Send the next blocks with the same `upload_id` and their `offset`. Make every block except the last a multiple of 4096 bytes; 1 MiB blocks work well. 3. Call `UploadTempFileCommit` with `upload_id` and `logical_size` (the file size). Leave `low_digest` and `high_digest` 0. If the commit fails, the upload is gone: upload the file again. 4. Create the document with `AddObjectWithFiles` and `files: { type: OBJ_FILE_SOURCE_TYPE_UPLOADED_FILE, data.uploaded_file: { upload_id, title, extension, size } }`. Verified with a 3 MiB file in 1 MiB blocks; the new document's file had the expected size and MD5. **Upload sessions.** * An upload session works only in the session and on the server process that created it. On M-Files Cloud send the stickiness cookie on all its calls. * It times out when idle (10 minutes by default). For a long upload, call [KeepUploadSessionAlive](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/KeepUploadSessionAlive/index.md) at least every `session_data.keep_upload_session_alive_interval_in_seconds` (300 seconds by default). * [RemoveUploadSession](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/RemoveUploadSession/index.md) discards an upload you no longer need. * Each request is subject to the 48 MiB limit; a 49 MiB block fails with `RESOURCE_EXHAUSTED`. ## Stateless uploads (switched off by default) [IRPCFileTransfer](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCFileTransfer/index.md) (`FileUploadBegin`, `FileUploadEnd`, `FileUploadAttach`) and the client-streaming [IRPCFileTransferStreaming](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCFileTransferStreaming/index.md) (`FileUploadWhole`, `FileUploadBlock`) upload without an upload session: the upload is identified by a `file_ref`. In M-Files Server 26.11 this feature is switched off by default: every method of the two services answers "Not implemented." (`E_NOTIMPL`), and the capability `ServerVaultCapabilities.file_upload_stateless_supported` is false. Upload with [UploadTempFile](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFile/index.md) and [UploadTempFileCommit](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFileCommit/index.md) as described above, and use the stateless services only on a server that reports the capability. --- # Versions and compatibility Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/versioning/ One published bundle of `.proto` files works against servers of many versions. The API grows by addition, and the server tells you what it supports. ## How the API changes The gRPC API is the API that the M-Files clients themselves use, so it evolves with the product. Almost all changes are additions that follow the [compatibility rules](#compatibility-rules) below, and code written for one version usually keeps working with newer servers. M-Files may also change the API in other ways as the product evolves, including deprecating and removing methods. Methods documented as "For M-Files components only" are internal and can change without notice. To see what a new version changes, compare its bundle with the one you built against: a diff of the `.proto` files shows the added, changed and removed methods and fields, and a coding agent can update your code from it. ## What the server tells you [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md) (anonymous) returns packed 64-bit versions, 16 bits each for major, minor, build and revision ([Connecting](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md#is-the-server-reachable) shows the decoding): | Field | Example | Meaning | |---|---|---| | `server_version` | 7318397729964032 = 26.11.16645.0 | The server's version. | | `oldest_compatible_client_version` | 5348033629585408 = 19.2.7357.0 | The oldest client version that the server currently accepts. | | `oldest_compatible_admin_client_version` | 19.2.7357.0 | The same for administrative clients. | | `oldest_compatible_replica_server_version` | 19.2.7357.0 | The same for replica servers. | [GetServerCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetServerCapabilities/index.md) (anonymous) returns [ServerVaultCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ServerVaultCapabilities/index.md): one boolean per optional feature (for example `search_results_sorting`, `vault_login_accounts`). A feature is available when its flag is true; a flag that the server's version does not know arrives as false. After login, `session_data.server_vault_capabilities` gives the same flags for the server and the vault. Prefer capability flags to version comparisons where a flag exists. Use `server_version` for behavior that has no flag, and to report the server version in your logs. ## What you tell the server [ClientData](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ClientData/index.md) goes with [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md): | Field | Send | |---|---| | `type` | `CLIENT_TYPE_SERVER_API` for a service or script; it is recorded with the session. | | `version` | Optional. A packed version, as in `QueryVersion`: the version of the server that the bundle you generated your stubs from was published for, for example 26.11.0.0. | | `capabilities` | [ClientCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ClientCapabilities/index.md): set a flag only for a newer behavior your code handles. Unset flags keep the server's older behavior. | | `language` | The language for server texts, for example `en`. | Why `version` matters: some administrative operations check it and refuse clients that report an older version (or none) with an "operation not supported by old client" error, and an administrator can make the server refuse logins from clients older than a configured version (`E_MFILES_INCOMPATIBLE_SERVER_VERSION`). A login with `version` unset works on a server with default settings. ## Compatibility rules M-Files evolves the gRPC contract by these rules. They describe current practice and what the product aims for, not a guarantee: * **Methods are bound by name** (`/MFiles./`). New methods are added; existing ones keep their names. Calling a method that the server does not have fails with `UNIMPLEMENTED`, so a client built from a newer bundle generally works against an older server as long as it does not call newer methods. * **Fields are bound by number.** New fields get new numbers. Field numbers are not reused, and fields keep their names (the mf-http-grpc gateway binds by name; see [HTTP access](https://developer.m-files.com/APIs/gRPC-API/runtime/http-access/index.md)). * **Unknown fields are ignored.** Protobuf skips fields it does not know, so an older server ignores a newer field you set, and your client ignores fields a newer server adds. * **Missing fields have default values**: empty string, zero, false, the enum's zero value, or an unset message. New fields are defined so that the default keeps the old behavior. Where a difference matters, a capability flag tells the two sides apart. * **Enums can grow.** Handle enum values your code does not know (proto3 keeps them as numbers). * **A changed method keeps its name.** When a method needs new parameters, its request message gets new fields under the rules above. Where that is not enough, a new method is added and the old one is marked as superseded. Superseded methods are left out of the published bundle and can later be removed from the server. ## The published bundle and server versions The bundle is generated from the server's interface definitions and is named after the server version it comes from (for example `26.11`). Its wire contract is identical to what that server serves. Against an older server, the methods and fields added after that server's version are missing: methods answer `UNIMPLEMENTED`, fields are ignored or arrive with default values. Check `server_version` or the capability flags before using a recent feature on a server you do not control, such as an on-premises installation. --- # HTTP access (mf-http-grpc) Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Runtime conventions](https://developer.m-files.com/APIs/gRPC-API/index.md#runtime) HTML version: https://developer.m-files.com/APIs/gRPC-API/runtime/http-access/ **Use native gRPC whenever your client can:** services, desktop applications, scripts and coding agents. The gateway described here exists for browsers and for environments that cannot make HTTP/2 gRPC calls. It adds a hop and a process, and JSON is larger and slower to process than protobuf. ## What it is `mf-http-grpc` is a Node.js HTTP server that M-Files Server starts and supervises next to its gRPC endpoint. It serves the M-Files web client, and it accepts the API's calls as JSON over plain HTTP POST and forwards them to the server's local gRPC endpoint. The services, methods, messages, session model and error codes are the ones of the gRPC API. | Deployment | Gateway address | |---|---| | On-premises | `http://:7767` by default: the gRPC port + 1, or the registry value `GRPCWEBPORT` | | M-Files Cloud | The vault host on port 443 (HTTPS), also under the prefix `/vnext` | ## Routes `POST /grpc/v1/`: the method name split before each run of capitals, lowercased and joined with `-`. Service names are not part of the path. | Method | Path | |---|---| | `QueryVersion` | `/grpc/v1/query-version` | | `GetObjectTypes` | `/grpc/v1/get-object-types` | | `GetNamedACLs` | `/grpc/v1/get-named-acls` | | `DownloadFile_Begin` | `/grpc/v1/download-file_-begin` | The body is the request message as JSON. Send `{}` for an empty request; an empty body fails with HTTP 500. An unknown path answers HTTP 404. ## Session Log in with `POST /grpc/v1/log-in` and the usual [LogInRequest](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md#request) as JSON. The reply's `session_data.session_id` is a base64 string (64 characters for the 48 bytes). Send it in the HTTP header `M-Files-Session` on later calls; the gateway turns it into the `mfiles-session-id-bin` metadata. `KeepAlive` and `LogOut` work as described in [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). ## JSON encoding | Proto | JSON | |---|---| | Field names | As in the `.proto` (snake_case) | | Enums | The value's name as a string: `"AUTH_DATA_TYPE_CREDENTIALS"` | | `int64`, `uint64` | Strings: `"server_version":"7318397729964032"` | | `bytes` | Base64 strings | | Unset fields in replies | Present with their default values (`false`, `0`, `""`) | A field of the wrong kind fails with HTTP 500 and a message that names it, for example `Request message serialization failure: .MFiles.ObjID.item_id: object expected`. `M-Files-Response-Format: protobuf` as a request header returns the reply as a binary protobuf message instead of JSON. ## Responses and errors Successful calls answer HTTP 200. Methods that return the vault's metadata structure version put it in the response header `m-files-metadata-version` (for example `1024`); see [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#response-metadata). Errors are JSON: ```json {"message":"Not found.","shortMessage":"Not found.","statusCode":500,"errorCode":2, "innerExceptionCode":-2147221493} ``` | Field | Meaning | |---|---| | `statusCode` | The HTTP status: 401 for an invalid session, 500 for M-Files errors. | | `errorCode` | The gRPC status code: 16 (`UNAUTHENTICATED`), 2 (`UNKNOWN`). | | `innerExceptionCode` | The M-Files error code (signed HRESULT) for `UNKNOWN`; see [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md). | | `message`, `shortMessage` | The error text. | ## Principles * It is the same API: every rule of the runtime pages (sessions, versions, error codes, file transfer through download and upload sessions) applies. * It forwards to the local endpoint of one server process. On M-Files Cloud its responses set the same `mfilesmsm` cookie as native gRPC responses do; keep cookies. * Prefer native gRPC for anything that is not a browser. --- # Python quickstart Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-python/ In about 15 minutes you generate the Python stubs of the M-Files gRPC API, configure a connection, and run a program that logs in to a vault and prints its 10 most recently modified documents. ## Prerequisites * Python 3.10 or newer. * A vault you can reach and its GUID, with braces (`{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}`). Don't know the GUID? [Find the vault by its name](https://developer.m-files.com/APIs/gRPC-API/guides/connect/index.md#find-the-vault-by-its-name) lists the vaults you can log in to, or ask your M-Files administrator. The endpoint: * **M-Files Cloud:** the vault's host name; the gRPC endpoint is that host on port 443, with TLS. * **On-premises:** the M-Files Server host and its gRPC port, 7766 unless your administrator changed it. See [Cloud and on-premises](https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/index.md). * A way to log in: an M-Files user account with a password, or single sign-on through M-Files Login Service. See [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). ## 1. Get the proto files Download the **[gRPC API bundle](https://developer.m-files.com/APIs/gRPC-API/downloads/mfiles-grpc-api-bundle.zip)** and unpack it. Its `proto` folder is the published API definition: one file per service (`mfiles/IRPCSearch.proto`, ...) plus the shared `mfiles/structs.proto` and `mfiles/enums.proto`, all in the package `MFiles`, with documentation comments. An M-Files installation also contains a proto file, in `Common\Web\GRPC\proto` under the installation folder. That is the legacy single combined file. Prefer the bundle: these samples and all code in the documentation use the bundle's file and module names (`mfiles.IRPCSearch_pb2`, `mfiles.structs_pb2`), which the combined file does not produce. ## 2. Generate the stubs The samples are in the `samples/python` folder of the bundle, next to the `proto` folder. ```shell cd samples/python python -m venv .venv .venv/bin/pip install -r requirements.txt # Windows: .venv\Scripts\pip install ... .venv/bin/python generate.py # Windows: .venv\Scripts\python generate.py ``` `generate.py` runs `grpc_tools.protoc` on every file of the bundle and writes the stubs into `gen/`. It finds the bundle at `../../proto`; pass another folder as an argument or set `MFILES_PROTO_DIR`. The pinned versions are in `requirements.txt` (`grpcio`, `grpcio-tools`, `protobuf`). To generate the stubs without `generate.py`, include the files in `mfiles/api/` too: the service files import `mfiles/api/annotations.proto`, and a `mfiles/*.proto` pattern misses it (the import then fails with `No module named 'mfiles.api'`): ```shell python -m grpc_tools.protoc -I proto --python_out=gen --grpc_python_out=gen \ proto/mfiles/*.proto proto/mfiles/api/*.proto ``` ## 3. Configure the connection The samples read environment variables. For M-Files Cloud (PowerShell): ```powershell $env:MFILES_ADDRESS = "myvault.example.com:443" $env:MFILES_VAULT = "{00000000-0000-0000-0000-000000000000}" $env:MFILES_USERNAME = "me" $env:MFILES_PASSWORD = Read-Host -Prompt 'Password' ``` For an on-premises server without TLS (bash): ```shell export MFILES_ADDRESS=myserver:7766 MFILES_TLS=false export MFILES_VAULT='{00000000-0000-0000-0000-000000000000}' export MFILES_USERNAME=me read -rs MFILES_PASSWORD && export MFILES_PASSWORD ``` | Variable | Meaning | |---|---| | `MFILES_ADDRESS` | `host:port` of the gRPC endpoint | | `MFILES_TLS` | `true` (default) or `false` for a server without TLS | | `MFILES_CA_CERT` | Optional: PEM file of root certificates to trust, for a self-signed server certificate | | `MFILES_VAULT` | The vault GUID, with braces | | `MFILES_USERNAME`, `MFILES_PASSWORD` | An M-Files user account (M-Files authentication) | | `MFILES_TOKEN`, `MFILES_AUTH_PLUGIN`, `MFILES_AUTH_SCOPE` | Instead of a password: an SSO token, the authentication plugin's name and its configuration scope | Use `MFILES_TLS=false` only for a server that has no certificate, such as a lab installation. **Single sign-on.** A vault that signs users in through M-Files Login Service, as is typical on M-Files Cloud, refuses a password login with "Authentication failed.". Set `MFILES_TOKEN` to an access token from the Login Service, and `MFILES_AUTH_PLUGIN` and `MFILES_AUTH_SCOPE` to the plugin and scope that the vault returns. [M-Files Login Service](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#m-files-login-service) shows how to get the token; the Python sample `recipes/login_service.py` does it end to end. ## 4. Run it ```shell .venv/bin/python quickstart.py ``` The output lists the documents, newest first. In the M-Files Sample Vault it starts like this: ```text Project Plan (template) (ID 445, version 1, modified 2012-06-07 12:01:57 UTC) Project Tasks - CRM Development (ID 444, version 1, modified 2012-06-07 11:54:29 UTC) Corporate Presentation (ID 443, version 1, modified 2012-06-07 11:40:14 UTC) ... ``` ## The code ```python from mfiles_client import MFilesClient, run # Also makes the stubs in gen/ importable. from mfiles_values import is_not_deleted, is_object_type from mfiles import IRPCSearch_pb2, IRPCSearch_pb2_grpc from mfiles import enums_pb2 as enums from mfiles import structs_pb2 as structs def main() -> None: # Connect and log in with the MFILES_* settings. Leaving the block logs out. with MFilesClient.from_env("M-Files gRPC quickstart") as client: request = IRPCSearch_pb2.SearchObjectsRequest( # Documents (object type 0) that are not deleted. conditions=[structs.SearchConditionArray( value=[is_object_type(0), is_not_deleted()])], # Newest first by property 21, "Last modified". The server sorts, then takes 10. sorting=structs.SearchResultsSorting(rules=structs.SortRuleValueArray(values=[ structs.SortRule(key=21, options=enums.SortRuleFlags(sort_descending=True))])), limit=10) # Service stubs run on the client's channel, which adds the session to each call. search = client.stub(IRPCSearch_pb2_grpc.IRPCSearchStub) response = search.SearchObjects(request) for item in response.results: version = item.object.version_info print(f"{version.title} (ID {item.object.object_info.obj_id.item_id.internal_id}, " f"version {version.version.internal_version}, " f"modified {version.last_modified_at_utc.ToDatetime():%Y-%m-%d %H:%M:%S} UTC)") if __name__ == "__main__": run(main) ``` *Source: [python/quickstart.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/quickstart.py)* * `MFilesClient.from_env` opens the channel and logs in. Leaving the `with` block logs out and closes the channel. * Every service has a generated stub, created on the client's channel with `client.stub(...)`. * [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md) takes exactly one [SearchConditionArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchConditionArray/index.md) of [SearchCondition](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchCondition/index.md) items, which must all match. Without the "not deleted" condition, users who may see deleted objects, such as administrators, get them too. * `limit=10` with a sort order returns the first 10 of all matches in that order. * The results are [SearchResultsItem](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchResultsItem/index.md) messages; `object` holds the version information (`version_info`) and the object information (`object_info`). * A failed call raises `MFilesError`, which names the M-Files error code. `is_object_type` and `is_not_deleted` are small builders from `mfiles_values.py`. They show the message shapes of the two conditions; lookups to object types use the value list ID -11: ```python def property_condition(property_def: int, condition_type: int, value: structs.TypedValue) -> structs.SearchCondition: """A condition on a property value, e.g. property 100 (Class) equals a lookup.""" return structs.SearchCondition( expression=structs.Expression( type=enums.EXPRESSION_TYPE_PROPERTY_VALUE, data=structs.ExpressionUnion(property_value=structs.ExpressionUnion.PropertyValueData( property_def=property_def))), type=condition_type, value=value) def status_condition(status: int, condition_type: int, value: structs.TypedValue) -> structs.SearchCondition: """A condition on a status value, e.g. the object type or the deleted flag.""" return structs.SearchCondition( expression=structs.Expression( type=enums.EXPRESSION_TYPE_STATUS_VALUE, data=structs.ExpressionUnion(status_value=structs.ExpressionUnion.StatusValueData( type=status))), type=condition_type, value=value) def is_object_type(object_type: int) -> structs.SearchCondition: """Objects of one object type. Value list -11 lists the object types.""" return status_condition(enums.STATUS_TYPE_OBJECT_TYPE, enums.CONDITION_TYPE_EQUAL, lookup(-11, object_type)) def is_not_deleted() -> structs.SearchCondition: """Objects that are not deleted.""" return status_condition(enums.STATUS_TYPE_IS_DELETED, enums.CONDITION_TYPE_EQUAL, boolean(False)) ``` *Source: [python/mfiles_values.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_values.py)* More about conditions: [Search](https://developer.m-files.com/APIs/gRPC-API/concepts/search/index.md). ## What the helper does for you `mfiles_client.py` hides the parts that the proto files cannot express. It opens the channel, with TLS unless `MFILES_TLS` is false, and sets the message size limits that M-Files' own client uses: responses of any size, requests up to 48 MiB (the server's default limit): ```python options = [("grpc.max_receive_message_length", MAX_RECEIVE_MESSAGE_BYTES), ("grpc.max_send_message_length", MAX_SEND_MESSAGE_BYTES)] if config.tls: root_certificates = Path(config.ca_cert).read_bytes() if config.ca_cert else None credentials = grpc.ssl_channel_credentials(root_certificates=root_certificates) self._channel = grpc.secure_channel(config.address, credentials, options=options) else: self._channel = grpc.insecure_channel(config.address, options=options) self.channel = grpc.intercept_channel(self._channel, _MetadataInterceptor(self)) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* It logs in with [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md). The response's `session_data.session_id` (48 bytes) identifies the session: ```python request = IRPCLogin_pb2.LogInRequest( environment_data=structs_pb2.EnvironmentData( client_name=self.client_name, host_platform="Python"), client_data=self._client_data(), login_data=structs_pb2.LoginData( server_hostname=self.config.host, vault_guid=self.config.vault), authentication_data=authentication) response = self.login.LogIn(request) if not response.session_data.session_id: raise RuntimeError("LogIn did not complete: the authentication needs more steps") # From now on, the interceptor sends this in "mfiles-session-id-bin" on every call. self.session_id = response.session_data.session_id ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* With a user name and password, `authentication_data` is: ```python authentication = structs_pb2.AuthDataClient( type=enums_pb2.AUTH_DATA_TYPE_CREDENTIALS, data=structs_pb2.AuthDataClientUnion(credentials=structs_pb2.Credentials( type=enums_pb2.CREDENTIALS_TYPE_MFILES, username=username, password=password))) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* Its interceptor adds call metadata to every call: `mfiles-is-remote-call: true`, a new random 16-byte `mfiles-activity-id-bin`, and after login the session ID as `mfiles-session-id-bin`. None of the request messages has a field for the session. A call that needs a session and does not have one fails with `UNAUTHENTICATED`. M-Files Cloud sets the cookie `mfilesmsm` to keep a client on one server instance; gRPC does not keep cookies, so the interceptor stores `set-cookie` values from the response metadata and sends them back in a `cookie` header on every later call. See [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness). When a call fails, the interceptor raises `MFilesError`, decoded from the `mfiles-error-serialized-bin` and `mfiles-error-text-bin` trailers: the gRPC status (`status`), the message (`message`), the M-Files error code of the top-level error (`hresult`; the last entry of the error stack that is not the generic `0x80040001`), every code of the stack (`codes`) and `has(code)`: ```python self.long_message: str = trailers.get(ERROR_TEXT_KEY, b"").decode("utf-8", "replace") self.stack = list(structs_pb2.RPCErrorInfoArray.FromString( trailers.get(ERROR_STACK_KEY, b"")).value) self.codes = [entry.error_code & 0xFFFFFFFF for entry in self.stack] # cause first # The top-level error, skipping entries that only pass the error on. self.hresult: Optional[int] = next( (code for code in reversed(self.codes) if code != E_PASS_THROUGH), self.codes[-1] if self.codes else None) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* Catch it like this: ```python try: objects.GetObjectInfoAndProperties(request) except MFilesError as error: if not error.has(0x8004000B): # not found raise print(f"{error.hresult_hex}: {error.message}") ``` [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md) lists the common codes and what to do about each. At the end, `close()` calls [LogOut](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogOut/index.md) and closes the channel. ## If it does not work | Symptom | Likely cause | |---|---| | `UNAVAILABLE`, "Tls handshake failed ... WRONG_VERSION_NUMBER" | TLS against a server without TLS: set `MFILES_TLS=false`, only for a lab server on a trusted network | | `UNIMPLEMENTED`, "Received http2 header with status: 404" | A plaintext call to a TLS endpoint, such as M-Files Cloud: remove `MFILES_TLS=false` | | `UNAVAILABLE`, "Connection refused" | Wrong host or port, or a firewall | | `UNKNOWN`, M-Files code `0x8004001A`, "Authentication failed." | Wrong user name or password, or the vault uses single sign-on (see step 3) | | `UNKNOWN`, M-Files code `0x80040020`, "The specified document vault does not exist." | Wrong `MFILES_VAULT` | | `UNAUTHENTICATED` | No session or an expired one: log in again | ## Next steps * [Connect](https://developer.m-files.com/APIs/gRPC-API/guides/connect/index.md): versions, capabilities, sessions and keep-alive. * [Vault structure](https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/index.md): find the IDs of object types, classes and properties. * [Search](https://developer.m-files.com/APIs/gRPC-API/guides/search/index.md), [Read an object](https://developer.m-files.com/APIs/gRPC-API/guides/read-object/index.md), [Download a file](https://developer.m-files.com/APIs/gRPC-API/guides/download-file/index.md). * [Create a document](https://developer.m-files.com/APIs/gRPC-API/guides/create-document/index.md), [Update properties](https://developer.m-files.com/APIs/gRPC-API/guides/update-properties/index.md), [Change the workflow state](https://developer.m-files.com/APIs/gRPC-API/guides/workflow-state/index.md). * [Page through results](https://developer.m-files.com/APIs/gRPC-API/guides/paging/index.md) and [Handle errors](https://developer.m-files.com/APIs/gRPC-API/guides/errors/index.md). --- # C# quickstart Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-csharp/ In about 15 minutes you build a .NET console app that connects to an M-Files vault over native gRPC, logs in, lists the 10 most recently modified documents and logs out. The app uses the official `Grpc.Net.Client` and `Google.Protobuf` packages, client code generated from the M-Files protos, and a small connection helper from the samples that you can copy into your own project. ## Prerequisites * The [.NET SDK](https://dotnet.microsoft.com/download) 10 or later. * A vault you can reach over gRPC: * **M-Files Cloud:** the vault's host name; the endpoint is `:443` with TLS. * **On-premises:** the server's host name and gRPC port, `7766` by default; plain HTTP/2 unless the administrator has enabled TLS. * The vault's GUID, for example `{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}`. Don't know the GUID? [Find the vault by its name](https://developer.m-files.com/APIs/gRPC-API/guides/connect/index.md#find-the-vault-by-its-name) lists the vaults you can log in to, or ask your M-Files administrator. * An M-Files user name and password. For single sign-on, see [Configure](#configure). [Cloud and on-premises](https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/index.md) and [Connecting to the server](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md) explain the endpoints in detail. ## Get the samples and the protos Download the [gRPC API bundle](https://developer.m-files.com/APIs/gRPC-API/downloads/mfiles-grpc-api-bundle.zip) and unpack it. It contains: * `proto/`: the API definition, one `.proto` file per service plus `mfiles/structs.proto`, `mfiles/enums.proto` and `mfiles/api/annotations.proto`, with the documentation as comments; * `samples/csharp/`: the solution this guide uses. An M-Files Server installation also has the API definition, in `\Common\Web\GRPC\proto`, as one combined file without documentation. It describes the same wire contract, but its generated C# namespace is `MFiles` rather than `MFiles.Grpc`. Prefer the bundle. ## Set up the project The samples are a solution with three projects: | Project | What it is | |---|---| | `Connection` | Class library: the generated client code and the `MFilesConnection` helper | | `Quickstart` | The console app of this guide | | `Recipes` | One console program per task recipe | `Connection` references three packages. `Grpc.Tools` is needed only at build time: ```xml ``` *Source: [csharp/Connection/Connection.csproj](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/Connection.csproj)* `Grpc.Tools` generates the client code from the protos on every build. `ProtoRoot` must be the folder that contains `mfiles/`, because the files import each other by that path: ```xml ``` *Source: [csharp/Connection/Connection.csproj](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/Connection.csproj)* `MFilesProtoDir` is set in `Directory.Build.props`: the `MFILES_PROTO_DIR` environment variable if it is set, otherwise `../../proto`, which is where the bundle keeps the protos. To use protos elsewhere, set `MFILES_PROTO_DIR` to the folder that contains `mfiles/structs.proto`. The generated classes are in the namespace `MFiles.Grpc`: a class per message, such as `SearchObjectsRequest`, and a client per service, such as `IRPCSearch.IRPCSearchClient`. To start a project of your own instead, run `dotnet new console`, add the three packages, and copy the `Protobuf` item and the `MFiles*.cs` files of `Connection`. ## Configure The samples read their settings from environment variables: | Variable | Value | |---|---| | `MFILES_ADDRESS` | `host:port`, for example `contoso.cloudvault.m-files.com:443` or `mfserver:7766` | | `MFILES_TLS` | `true` (default), or `false` for a server without TLS | | `MFILES_CA_CERT` | Optional: a PEM file of the root certificate to trust, for a server whose certificate comes from a private CA | | `MFILES_VAULT` | The vault GUID, with braces | | `MFILES_USERNAME`, `MFILES_PASSWORD` | An M-Files user account | M-Files Cloud: ```powershell $env:MFILES_ADDRESS = 'contoso.cloudvault.m-files.com:443' $env:MFILES_VAULT = '{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}' $env:MFILES_USERNAME = 'alice' $env:MFILES_PASSWORD = Read-Host -Prompt 'Password' ``` On-premises without TLS: ```powershell $env:MFILES_ADDRESS = 'mfserver:7766' $env:MFILES_TLS = 'false' $env:MFILES_VAULT = '{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}' $env:MFILES_USERNAME = 'alice' $env:MFILES_PASSWORD = Read-Host -Prompt 'Password' ``` **Single sign-on.** A vault that signs users in through M-Files Login Service, as is typical on M-Files Cloud, refuses a password login with "Authentication failed.". Set `MFILES_TOKEN` to an access token from the Login Service, and `MFILES_AUTH_PLUGIN` and `MFILES_AUTH_SCOPE` to the plugin and scope that the vault returns. [M-Files Login Service](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#m-files-login-service) shows how to get the token; the Python sample `recipes/login_service.py` does it end to end. ## Run ```powershell cd samples\csharp dotnet run --project Quickstart ``` The first build restores the packages and generates the client code, which takes a minute. The output lists the documents, newest first. In the M-Files Sample Vault it starts like this: ```text Project Plan (template) (ID 445, version 1, modified 2012-06-07 12:01:57 UTC) Project Tasks - CRM Development (ID 444, version 1, modified 2012-06-07 11:54:29 UTC) Corporate Presentation (ID 443, version 1, modified 2012-06-07 11:40:14 UTC) ... ``` ## The code ```csharp using MFiles.Grpc; using MFiles.Grpc.Samples; using static MFiles.Grpc.Samples.MFilesValues; // Connect and log in with the MFILES_* settings. Disposing the connection logs out. await using var connection = await MFilesConnection.ConnectAsync( MFilesSettings.FromEnvironment(), clientName: "M-Files gRPC quickstart"); var request = new SearchObjectsRequest { // Documents (object type 0) that are not deleted. Conditions = { new SearchConditionArray { Value = { IsObjectType(0), IsNotDeleted() } } }, // Newest first by property 21, "Last modified". The server sorts, then takes 10. Sorting = new SearchResultsSorting { Rules = new SortRuleValueArray { Values = { new SortRule { Key = 21, Options = new SortRuleFlags { SortDescending = true } } }, }, }, Limit = 10, }; // Service clients run on the connection's call invoker, which adds the session to each call. var search = new IRPCSearch.IRPCSearchClient(connection.CallInvoker); var response = await search.SearchObjectsAsync(request); foreach (var item in response.Results) { var version = item.Object.VersionInfo; Console.WriteLine($"{version.Title} (ID {item.Object.ObjectInfo.ObjId.ItemId.InternalId}, " + $"version {version.Version.InternalVersion}, " + $"modified {version.LastModifiedAtUtc.ToDateTime():yyyy-MM-dd HH:mm:ss} UTC)"); } ``` *Source: [csharp/Quickstart/Program.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Quickstart/Program.cs)* * `MFilesConnection.ConnectAsync` opens the channel and logs in. `await using` logs out and closes the channel at the end. * Every service is a generated client class created on `connection.CallInvoker`. * The search has one array of conditions; all conditions in an array must match. Without the "not deleted" condition, users who may see deleted objects get them too. * `Limit = 10` with a sort order returns the first 10 of all matches in that order. * A failure ends the program with an `MFilesException` that names the M-Files error code. `IsObjectType` and `IsNotDeleted` are small builders from the helper. They show the message shapes of the two conditions: ```csharp /// A condition on a property value, e.g. property 100 (Class) equals a lookup. public static SearchCondition PropertyCondition( int propertyDef, ConditionType type, TypedValue value) => new() { Expression = new Expression { Type = ExpressionType.PropertyValue, Data = new ExpressionUnion { PropertyValue = new ExpressionUnion.Types.PropertyValueData { PropertyDef = propertyDef, }, }, }, Type = type, Value = value, }; /// A condition on a status value, e.g. the object type or the deleted flag. public static SearchCondition StatusCondition( StatusType status, ConditionType type, TypedValue value) => new() { Expression = new Expression { Type = ExpressionType.StatusValue, Data = new ExpressionUnion { StatusValue = new ExpressionUnion.Types.StatusValueData { Type = status }, }, }, Type = type, Value = value, }; /// Objects of one object type. Value list -11 lists the object types. public static SearchCondition IsObjectType(int objectType) => StatusCondition(StatusType.ObjectType, ConditionType.Equal, Lookup(-11, objectType)); /// Objects that are not deleted. public static SearchCondition IsNotDeleted() => StatusCondition(StatusType.IsDeleted, ConditionType.Equal, Boolean(false)); ``` *Source: [csharp/Connection/MFilesValues.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesValues.cs)* ## What the helper does gRPC alone is not enough to talk to M-Files: part of the contract travels in call metadata, and errors arrive in trailers. `MFilesConnection` takes care of that. **The channel.** TLS or plain HTTP/2, an optional private root certificate, and the message size limits that M-Files' own client uses: responses of any size, requests up to 48 MiB. With an `http://` address, .NET makes an unencrypted HTTP/2 connection; no extra switch is needed on .NET 10. `SocketsHttpHandler` keeps cookies, so the affinity cookie that M-Files Cloud sets (`mfilesmsm`) goes back on later calls, as [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness) requires: ```csharp var handler = new SocketsHttpHandler { // M-Files Cloud answers with a server affinity cookie (mfilesmsm); the handler's // cookie container sends it back on later calls. UseCookies = true, // gRPC multiplexes calls on one HTTP/2 connection; allow more when it is busy. EnableMultipleHttp2Connections = true, KeepAlivePingDelay = TimeSpan.FromSeconds(60), KeepAlivePingTimeout = TimeSpan.FromSeconds(30), }; if (settings.CaCertificatePath is { } caPath) { // Trust this root (e.g. an on-premises server's own CA) instead of the system's roots. var roots = new X509Certificate2Collection(); roots.ImportFromPemFile(caPath); handler.SslOptions.CertificateChainPolicy = new X509ChainPolicy { TrustMode = X509ChainTrustMode.CustomRootTrust, // A private CA usually publishes no revocation list. RevocationMode = X509RevocationMode.NoCheck, }; handler.SslOptions.CertificateChainPolicy.CustomTrustStore.AddRange(roots); } // "http://" makes an unencrypted HTTP/2 connection (lab servers only). string scheme = settings.UseTls ? "https" : "http"; var channel = GrpcChannel.ForAddress($"{scheme}://{settings.Address}", new GrpcChannelOptions { HttpHandler = handler, // No limit (null) on received messages: responses can be much larger than gRPC's // default of 4 MiB. MaxReceiveMessageSize = null, MaxSendMessageSize = MaxSendMessageSize, }); ``` *Source: [csharp/Connection/MFilesConnection.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesConnection.cs)* **Call metadata.** A call invoker adds the M-Files metadata to every call, and after login the session ID: ```csharp /// /// The metadata M-Files expects on every call. gRPC base64-encodes "-bin" values itself. /// private CallOptions WithMFilesMetadata(CallOptions options) { var headers = new Metadata(); foreach (var entry in options.Headers ?? []) headers.Add(entry); // The caller is not one of the server's own local processes. headers.Add("mfiles-is-remote-call", "true"); // A new 16-byte ID per call; it identifies the call in the server's logs. headers.Add("mfiles-activity-id-bin", Guid.NewGuid().ToByteArray()); // After LogIn: the session ID, unchanged. if (connection.SessionId is { } sessionId) headers.Add("mfiles-session-id-bin", sessionId.ToByteArray()); return options.WithHeaders(headers); } ``` *Source: [csharp/Connection/MFilesCallInvoker.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesCallInvoker.cs)* **Login.** `LogIn` with M-Files credentials returns a session; its `session_id` goes into `mfiles-session-id-bin` from then on: ```csharp var authentication = new AuthDataClient { Type = AuthDataType.Credentials, Data = new AuthDataClientUnion { Credentials = new Credentials { Username = username, Password = password, Type = CredentialsType.Mfiles, // M-Files authentication }, }, }; ``` *Source: [csharp/Connection/MFilesConnection.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesConnection.cs)* ```csharp var request = new LogInRequest { EnvironmentData = new EnvironmentData { ClientName = clientName, HostPlatform = ".NET" }, ClientData = new ClientData { Type = ClientType.ServerApi, Language = "en", // Needed for the 32-byte attempt ID of a token login. Capabilities = new ClientCapabilities { LongAuthenticationAttemptIdentifier = true }, }, LoginData = new LoginData { ServerHostname = _host, VaultGuid = vault }, AuthenticationData = authentication, }; var login = new IRPCLogin.IRPCLoginClient(CallInvoker); var response = await login.LogInAsync(request, cancellationToken: cancellationToken); if (response.SessionData is not { SessionId.IsEmpty: false } session) throw new InvalidOperationException( "LogIn returned no session; this helper supports one-step logins only."); // Every later call sends this session ID in mfiles-session-id-bin (see MFilesCallInvoker). Session = session; ``` *Source: [csharp/Connection/MFilesConnection.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesConnection.cs)* **Errors.** A failed call throws `MFilesException`, decoded from the `mfiles-error-serialized-bin` and `mfiles-error-text-bin` trailers: the gRPC status (`StatusCode`), the message, the M-Files error code of the top-level error (`ErrorCode`, an HRESULT such as `0x8004000B` "Not found."; the last entry of the error stack that is not the generic `0x80040001`), every code of the stack (`ErrorCodes`) and `Has(code)`: ```csharp /// The generic code of an error that is only passed up the stack. private const int PassThrough = unchecked((int)0x80040001); /// /// Decodes the M-Files error from the trailers of a failed call. /// public static MFilesException FromRpcException(RpcException e) { // The error stack: a serialized MFiles.RPCErrorInfoArray, the original error first. var errors = e.Trailers.GetValueBytes("mfiles-error-serialized-bin") is { } stack ? RPCErrorInfoArray.Parser.ParseFrom(stack).Value.ToList() : []; // The long error message: UTF-8 text in a binary header. string? details = e.Trailers.GetValueBytes("mfiles-error-text-bin") is { } text ? System.Text.Encoding.UTF8.GetString(text) : null; // The call failed with the top-level error, the last entry; the status detail is its // message. Skip entries that only pass the error on. int? errorCode = errors.Count > 0 ? errors[^1].ErrorCode : null; if (errors.LastOrDefault(error => error.ErrorCode != PassThrough) is { } topLevel) errorCode = topLevel.ErrorCode; return new MFilesException(e, errorCode, details, errors); } ``` *Source: [csharp/Connection/MFilesException.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Connection/MFilesException.cs)* Catch it like this: ```csharp try { await objects.GetObjectInfoAndPropertiesAsync(request); } catch (MFilesException e) when (e.Has(0x8004000B)) // not found { Console.WriteLine($"{e.ErrorCodeHex}: {e.Message}"); } ``` [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md) lists the common codes and what to do about each. ## Troubleshooting `dotnet run --project Recipes -- connect` calls `QueryVersion` before it logs in, so it tells connection problems from login problems. | Symptom | Cause | |---|---| | Build error "M-Files protos not found" | Set `MFILES_PROTO_DIR` to the folder that contains `mfiles/structs.proto`. | | Status `Unavailable`, "Error connecting to subchannel" | Wrong host or port, or a firewall in between. | | Status `Internal`, "The SSL connection could not be established" ... `UntrustedRoot` | The server's certificate comes from a CA your machine does not trust. Set `MFILES_CA_CERT` to its root certificate (PEM). | | Status `Internal`, "The SSL connection could not be established" ... "corrupted frame" | TLS against a server without TLS: set `MFILES_TLS=false`, only for a lab server on a trusted network. | | Status `Unimplemented`, "HTTP status code: 404", against M-Files Cloud | `MFILES_TLS=false`; M-Files Cloud needs TLS. | | M-Files error `0x8004001A`, "Authentication failed." | Wrong user name or password, or the vault uses single sign-on (see [Configure](#configure)). | ## Next steps * Run the task recipes: `dotnet run --project Recipes -- `, with `connect`, `vault-structure`, `search`, `read-object`, `download-file`, `create-document`, `update-properties`, `workflow-state`, `paging` or `errors`. The samples' `README.md` lists their arguments. * Learn the model: [Vault and metadata structure](https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/index.md), [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md), [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md) and [Search](https://developer.m-files.com/APIs/gRPC-API/concepts/search/index.md). * Keep a session healthy in a long-running program: [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). * Find any method in the reference, starting with [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md) and [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md). --- # Go quickstart Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-go/ In about 15 minutes you generate Go code from the M-Files protos, configure a connection and run a program that logs in to a vault, lists its 10 most recently modified documents and logs out. The program uses the official `google.golang.org/grpc` and `google.golang.org/protobuf` modules and `mfilesclient`, a small connection helper from the samples that you can copy into your own project. ## Prerequisites * [Go](https://go.dev/dl/) 1.26 or later: `google.golang.org/grpc` v1.85.0 requires it. * `protoc`, the protocol buffer compiler. Python's `grpcio-tools` package includes one (`pip install grpcio-tools`); the generate scripts use it when `protoc` is not on `PATH`. * The Go plugins for `protoc`: ```shell go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.12 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.6.2 ``` * A vault you can reach over gRPC, and its GUID with braces (`{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}`). Don't know the GUID? [Find the vault by its name](https://developer.m-files.com/APIs/gRPC-API/guides/connect/index.md#find-the-vault-by-its-name) lists the vaults you can log in to, or ask your M-Files administrator. The endpoint: * **M-Files Cloud:** the vault's host name; the endpoint is `:443` with TLS. * **On-premises:** the server's host name and gRPC port, `7766` by default; plain HTTP/2 unless the administrator has enabled TLS. * An M-Files user name and password, or a token from the vault's identity provider. [Cloud and on-premises](https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/index.md) and [Connecting to the server](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md) explain the endpoints in detail. ## Get the samples and the protos Download the [gRPC API bundle](https://developer.m-files.com/APIs/gRPC-API/downloads/mfiles-grpc-api-bundle.zip) and unpack it. It contains: * `proto/`: the API definition, one `.proto` file per service plus `mfiles/structs.proto`, `mfiles/enums.proto` and `mfiles/api/annotations.proto`, with the documentation as comments; * `samples/go/`: the Go module this guide uses. An M-Files Server installation also has the API definition, in `\Common\Web\GRPC\proto`, as one combined legacy file without documentation. It describes the same wire contract, but it does not produce the Go package path these samples import. Prefer the bundle. ## Generate the code ```powershell cd samples\go .\generate.ps1 # PowerShell; in bash: ./generate.sh ``` The script runs `protoc` with `protoc-gen-go` and `protoc-gen-go-grpc` on every file of the bundle and writes the code into `gen/`. It finds the bundle at `../../proto`; pass another folder with `-ProtoDir` (or as the first argument of `generate.sh`), or set `MFILES_PROTO_DIR`. The bundle's `go_package` option puts all services, messages and enums into one package, `github.com/M-Files/mfiles-grpc-api/go/mfiles`. The script makes `gen/` a module of its own with that path, and the samples' `go.mod` points to it: ```go // The generated code: a module of its own in ./gen, written by generate.ps1 or generate.sh. replace github.com/M-Files/mfiles-grpc-api => ./gen ``` *Source: [go/go.mod](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/go.mod)* Your code imports the package as `"github.com/M-Files/mfiles-grpc-api/go/mfiles"`. The package is large: the first build takes a while, later builds come from the cache. To start a project of your own, copy `gen/`, the `replace` directive and the `mfilesclient` folder, and require `google.golang.org/grpc` and `google.golang.org/protobuf` in your `go.mod`. ## Configure The samples read their settings from environment variables: | Variable | Value | |---|---| | `MFILES_ADDRESS` | `host:port`, for example `contoso.cloudvault.m-files.com:443` or `mfserver:7766` | | `MFILES_TLS` | `true` (default), or `false` for a server without TLS | | `MFILES_CA_CERT` | Optional: a PEM file of root certificates to trust, for a server whose certificate comes from a private CA | | `MFILES_VAULT` | The vault GUID, with braces | | `MFILES_USERNAME`, `MFILES_PASSWORD` | An M-Files user account (M-Files authentication) | | `MFILES_TOKEN`, `MFILES_AUTH_PLUGIN`, `MFILES_AUTH_SCOPE` | Instead of a password: a token from the vault's identity provider, the authentication plugin's name and its configuration scope | M-Files Cloud (PowerShell): ```powershell $env:MFILES_ADDRESS = 'contoso.cloudvault.m-files.com:443' $env:MFILES_VAULT = '{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}' $env:MFILES_USERNAME = 'alice' $env:MFILES_PASSWORD = Read-Host -Prompt 'Password' ``` On-premises without TLS (bash): ```shell export MFILES_ADDRESS=mfserver:7766 MFILES_TLS=false export MFILES_VAULT='{7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575}' export MFILES_USERNAME=alice read -rs MFILES_PASSWORD && export MFILES_PASSWORD ``` Use `MFILES_TLS=false` only for a server that has no certificate, such as a lab installation. **Single sign-on.** A vault that signs users in through M-Files Login Service, as is typical on M-Files Cloud, refuses a password login with "Authentication failed.". Set `MFILES_TOKEN` to an access token from the Login Service, and `MFILES_AUTH_PLUGIN` and `MFILES_AUTH_SCOPE` to the plugin and scope that the vault returns. [M-Files Login Service](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#m-files-login-service) shows how to get the token; the Python sample `recipes/login_service.py` does it end to end. Getting an SSO token is up to your application; see [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). ## Run ```shell go run ./quickstart ``` The output lists the documents, newest first. In the M-Files Sample Vault it starts like this: ```text Project Plan (template) (ID 445, version 1, modified 2012-06-07 12:01:57 UTC) Project Tasks - CRM Development (ID 444, version 1, modified 2012-06-07 11:54:29 UTC) Corporate Presentation (ID 443, version 1, modified 2012-06-07 11:40:14 UTC) ... ``` ## The code ```go // Connect and log in with the settings in the MFILES_* environment variables. cfg, err := mfilesclient.ConfigFromEnv() if err != nil { return err } cfg.ClientName = "M-Files gRPC quickstart" client, err := mfilesclient.Connect(ctx, cfg) if err != nil { return err } defer client.Close() // logs out and closes the channel // Documents (object type 0) that are not deleted, sorted by "Last modified" (property 21), // newest first. The server sorts before it applies the limit. search := mfiles.NewIRPCSearchClient(client.Conn) response, err := search.SearchObjects(ctx, &mfiles.SearchObjectsRequest{ Conditions: mfilesclient.AllOf(mfilesclient.IsObjectType(0), mfilesclient.IsNotDeleted()), Sorting: mfilesclient.SortBy(21, true), Limit: 10, }) if err != nil { return err } for _, item := range response.GetResults() { object := item.GetObject() version := object.GetVersionInfo() fmt.Printf("%s (ID %d, version %d, modified %s UTC)\n", version.GetTitle(), object.GetObjectInfo().GetObjId().GetItemId().GetInternalId(), version.GetVersion().GetInternalVersion(), version.GetLastModifiedAtUtc().AsTime().Format(time.DateTime)) } ``` *Source: [go/quickstart/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/quickstart/main.go)* * `mfilesclient.Connect` creates the channel and logs in. `client.Close` logs out and closes the channel. * Every service has a generated client, created on the channel: `mfiles.NewIRPCSearchClient(client.Conn)`. * The search has one array of conditions; all conditions in an array must match. Without the "not deleted" condition, users who may see deleted objects get them too. * `Limit: 10` with a sort order returns the first 10 of all matches in that order. * A failed call returns an `*mfilesclient.Error` that names the M-Files error code. `IsObjectType` and `IsNotDeleted` are small builders from the helper. They show the message shapes of the two conditions: ```go // StatusCondition compares a status value of the object, such as its object type or whether // it is deleted. func StatusCondition(status mfiles.StatusType, condition mfiles.ConditionType, value *mfiles.TypedValue) *mfiles.SearchCondition { return &mfiles.SearchCondition{ Expression: &mfiles.Expression{ Type: mfiles.ExpressionType_EXPRESSION_TYPE_STATUS_VALUE, Data: &mfiles.ExpressionUnion{Data: &mfiles.ExpressionUnion_StatusValue{ StatusValue: &mfiles.ExpressionUnion_StatusValueData{Type: status}, }}, }, Type: condition, Value: value, } } // PropertyCondition compares a property value of the object, such as its class (property 100) // or "Last modified" (property 21). func PropertyCondition(propertyDef int32, condition mfiles.ConditionType, value *mfiles.TypedValue) *mfiles.SearchCondition { return &mfiles.SearchCondition{ Expression: &mfiles.Expression{ Type: mfiles.ExpressionType_EXPRESSION_TYPE_PROPERTY_VALUE, Data: &mfiles.ExpressionUnion{Data: &mfiles.ExpressionUnion_PropertyValue{ PropertyValue: &mfiles.ExpressionUnion_PropertyValueData{PropertyDef: propertyDef}, }}, }, Type: condition, Value: value, } } // IsObjectType matches objects of one object type (0 = document). Value list -11 lists the // object types. func IsObjectType(objectType int32) *mfiles.SearchCondition { return StatusCondition(mfiles.StatusType_STATUS_TYPE_OBJECT_TYPE, mfiles.ConditionType_CONDITION_TYPE_EQUAL, Lookup(-11, objectType)) } // IsNotDeleted matches objects that are not deleted. func IsNotDeleted() *mfiles.SearchCondition { return StatusCondition(mfiles.StatusType_STATUS_TYPE_IS_DELETED, mfiles.ConditionType_CONDITION_TYPE_EQUAL, Boolean(false)) } ``` *Source: [go/mfilesclient/search.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/search.go)* ## Go and the generated code * **Enum values carry their type's name**: `DATATYPE_TEXT` of the enum `Datatype` is `mfiles.Datatype_DATATYPE_TEXT`. * **Each member of a `oneof` is a wrapper type.** A text value is `&mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Text{Text: "..."}}`. Read one with a type switch on `GetData()`. * **Use the getters** (`GetObject()`, `GetVersionInfo()`, ...) to read responses: they return zero values instead of panicking when a message field is unset. The helper has constructors for the common values: ```go // Text is a single-line text value. func Text(value string) *mfiles.TypedValue { return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_TEXT, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Text{Text: value}}, } } // Integer is an integer value. func Integer(value int32) *mfiles.TypedValue { return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_INTEGER, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Integer{Integer: value}}, } } // Boolean is a yes/no value. func Boolean(value bool) *mfiles.TypedValue { return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_BOOLEAN, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Boolean{Boolean: value}}, } } // Timestamp is a date and time value, such as "Last modified" (property 21). func Timestamp(value time.Time) *mfiles.TypedValue { return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_TIMESTAMP, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Timestamp{ Timestamp: timestamppb.New(value), }}, } } // Lookup is a single-select lookup value: the item with the given ID in a value list (or an // object of the object type with that ID). func Lookup(valueList, item int32) *mfiles.TypedValue { return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_LOOKUP, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_Lookup{ Lookup: lookupItem(valueList, item), }}, } } // MultiSelectLookup is a multi-select lookup value. func MultiSelectLookup(valueList int32, items ...int32) *mfiles.TypedValue { lookups := make([]*mfiles.Lookup, 0, len(items)) for _, item := range items { lookups = append(lookups, lookupItem(valueList, item)) } return &mfiles.TypedValue{ Type: mfiles.Datatype_DATATYPE_MULTI_SELECT_LOOKUP, Data: &mfiles.TypedValueUnion{Data: &mfiles.TypedValueUnion_MultiSelectLookup{ MultiSelectLookup: &mfiles.MultiSelectLookup{Values: lookups}, }}, } } func lookupItem(valueList, item int32) *mfiles.Lookup { return &mfiles.Lookup{ ValueListItemInfo: &mfiles.ItemInfo{ObjId: ObjID(valueList, item)}, Version: &mfiles.ObjVerVersion{ Type: mfiles.ObjVerVersionType_OBJ_VER_VERSION_TYPE_LATEST, InternalVersion: -1, }, } } ``` *Source: [go/mfilesclient/values.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/values.go)* ## What the helper does gRPC alone is not enough to talk to M-Files: part of the contract travels in call metadata, and errors arrive in trailers. `mfilesclient` takes care of that. **The channel.** TLS with the system's root certificates plus an optional PEM file, or no TLS at all, and the message size limits that M-Files' own client uses: responses of any size instead of gRPC's default 4 MiB, requests up to 48 MiB (the server's default limit). Two interceptors handle every call: ```go // Dial creates the channel to cfg.Address. It does not log in and does not contact the server // yet: the first call does. func Dial(cfg Config) (*Client, error) { transport, err := transportCredentials(cfg) if err != nil { return nil, err } client := &Client{host: cfg.host()} client.Conn, err = grpc.NewClient(cfg.Address, grpc.WithTransportCredentials(transport), grpc.WithDefaultCallOptions( grpc.MaxCallRecvMsgSize(MaxReceiveMessageSize), grpc.MaxCallSendMsgSize(MaxSendMessageSize)), // Every call carries the M-Files metadata; failed calls return *Error. grpc.WithChainUnaryInterceptor(client.unaryInterceptor), grpc.WithChainStreamInterceptor(client.streamInterceptor)) if err != nil { return nil, err } return client, nil } // transportCredentials is TLS with the system's root certificates, plus cfg.CACertFile when set; // or no TLS at all when cfg.TLS is false. func transportCredentials(cfg Config) (credentials.TransportCredentials, error) { if !cfg.TLS { return insecure.NewCredentials(), nil } roots, err := x509.SystemCertPool() if err != nil { roots = x509.NewCertPool() } if cfg.CACertFile != "" { pem, err := os.ReadFile(cfg.CACertFile) if err != nil { return nil, err } if !roots.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("%s: no PEM certificates found", cfg.CACertFile) } } return credentials.NewTLS(&tls.Config{RootCAs: roots, MinVersion: tls.VersionTLS12}), nil } ``` *Source: [go/mfilesclient/client.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/client.go)* **Call metadata.** Every call carries `mfiles-is-remote-call: true` and a new random 16-byte `mfiles-activity-id-bin`; after login also the session ID in `mfiles-session-id-bin`. In Go the value of a `-bin` key is the raw bytes in a string; gRPC base64-encodes it on the wire. M-Files Cloud sets the cookie `mfilesmsm` to keep a client on one server instance; gRPC does not keep cookies, so the interceptor stores `set-cookie` values and sends them back in a `cookie` header (see [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness)): ```go // withMetadata adds the M-Files call metadata to an outgoing call: the remote-call flag, a new // activity ID, the session ID once logged in, and the cookies the server has set. func (c *Client) withMetadata(ctx context.Context) context.Context { activityID := make([]byte, 16) _, _ = rand.Read(activityID) pairs := []string{headerRemoteCall, "true", headerActivityID, string(activityID)} if session := c.sessionID(); len(session) > 0 { pairs = append(pairs, headerSessionID, string(session)) } if cookies := c.cookieHeader(); cookies != "" { pairs = append(pairs, headerCookie, cookies) } return metadata.AppendToOutgoingContext(ctx, pairs...) } // unaryInterceptor adds the metadata to every unary call, keeps the cookies and the metadata // version the server returns, and turns a failure into *Error. func (c *Client) unaryInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { var header, trailer metadata.MD opts = append(opts, grpc.Header(&header), grpc.Trailer(&trailer)) err := invoker(c.withMetadata(ctx), method, req, reply, cc, opts...) c.readResponseMetadata(header, trailer) return decodeError(err, trailer) } ``` *Source: [go/mfilesclient/metadata.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/metadata.go)* **Login.** [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) binds the session to the vault in `login_data.vault_guid`. Its response's `session_data.session_id` (48 bytes) goes into `mfiles-session-id-bin` from then on: ```go // LogInWith logs in to a vault. From then on every call carries the session. func (c *Client) LogInWith(ctx context.Context, vault string, auth *mfiles.AuthDataClient, clientName string) (*mfiles.SessionData, error) { if clientName == "" { clientName = filepath.Base(os.Args[0]) } response, err := mfiles.NewIRPCLoginClient(c.Conn).LogIn(ctx, &mfiles.LogInRequest{ EnvironmentData: &mfiles.EnvironmentData{ClientName: clientName, HostPlatform: "Go"}, ClientData: &mfiles.ClientData{ Type: mfiles.ClientType_CLIENT_TYPE_SERVER_API, Language: "en", // A plugin (token) login uses the 32-byte form of the attempt identifier. Capabilities: &mfiles.ClientCapabilities{LongAuthenticationAttemptIdentifier: true}, }, LoginData: &mfiles.LoginData{ServerHostname: c.host, VaultGuid: vault}, AuthenticationData: auth, }) if err != nil { return nil, err } session := response.GetSessionData() if len(session.GetSessionId()) == 0 { // A plugin that needs more than one round trip answers without a session. return nil, errors.New("LogIn returned no session: multi-step authentication is not supported") } // From now on the interceptor sends session_id in mfiles-session-id-bin on every call. c.mu.Lock() c.session = session c.mu.Unlock() return session, nil } ``` *Source: [go/mfilesclient/client.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/client.go)* With a user name and password, `authentication_data` is: ```go // PasswordAuth is the authentication data of an M-Files user name and password. func PasswordAuth(username, password string) *mfiles.AuthDataClient { return &mfiles.AuthDataClient{ Type: mfiles.AuthDataType_AUTH_DATA_TYPE_CREDENTIALS, Data: &mfiles.AuthDataClientUnion{Data: &mfiles.AuthDataClientUnion_Credentials{ Credentials: &mfiles.Credentials{ Type: mfiles.CredentialsType_CREDENTIALS_TYPE_MFILES, Username: username, Password: password, }, }}, } } ``` *Source: [go/mfilesclient/client.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/client.go)* **Errors.** A failed call returns `*mfilesclient.Error`, decoded from the `mfiles-error-serialized-bin` and `mfiles-error-text-bin` trailers: the gRPC status (`Status`), the message, the M-Files error code of the top-level error (`HResult`, such as `0x8004000B` "Not found."; the last entry of the error stack that is not the generic `0x80040001`), every code of the stack (`Codes`) and `Has(code)`. It keeps the gRPC status, so `status.Code(err)` still works: ```go // Error is a failed M-Files call. Test for it with errors.As; status.Code(err) still gives the // gRPC status code. type Error struct { Status *status.Status // the gRPC status: Unknown for M-Files errors HResult uint32 // the M-Files error code, e.g. 0x8004000B; 0 when unknown Codes []uint32 // the codes of the error stack, the original error first Message string // the error message (the gRPC status message) Details string // the long error message with the server's error stack Stack []*mfiles.RPCErrorInfo // the server's error stack, the original error first } func (e *Error) Error() string { if len(e.Codes) == 0 { return fmt.Sprintf("gRPC error %s: %s", e.Status.Code(), e.Message) } return fmt.Sprintf("M-Files error 0x%08X (%s): %s", e.HResult, e.Status.Code(), e.Message) } // SignedHResult is the error code as a signed 32-bit integer, e.g. -2147221493. func (e *Error) SignedHResult() int32 { return int32(e.HResult) } // GRPCStatus lets status.Code and status.FromError see through the *Error. func (e *Error) GRPCStatus() *status.Status { return e.Status } // Has reports whether code is anywhere in the error stack. func (e *Error) Has(code uint32) bool { for _, c := range e.Codes { if c == code { return true } } return false } // AsError returns the *Error in err's chain, if there is one. func AsError(err error) (*Error, bool) { var mfilesErr *Error ok := errors.As(err, &mfilesErr) return mfilesErr, ok } // decodeError turns a failed call into *Error, using the error trailers when present. func decodeError(err error, trailer metadata.MD) error { if err == nil { return nil } st, ok := status.FromError(err) if !ok || st.Code() == codes.OK { return err } result := &Error{Status: st, Message: st.Message()} if values := trailer.Get(headerErrorText); len(values) > 0 { result.Details = values[0] } if values := trailer.Get(headerErrorStack); len(values) > 0 { stack := &mfiles.RPCErrorInfoArray{} if proto.Unmarshal([]byte(values[0]), stack) == nil { result.Stack = stack.GetValue() } } for _, info := range result.Stack { result.Codes = append(result.Codes, uint32(info.GetErrorCode())) } result.HResult = errorCode(result.Codes) return result } // errorPassThrough is the generic code of an error that is only passed up the stack; it does // not say what went wrong. const errorPassThrough = 0x80040001 // errorCode is the code of the top-level error, the last entry of the stack: the error the // status message describes, such as 0x800407E4 for "Access denied." Entries with the generic // code 0x80040001 are skipped. func errorCode(stack []uint32) uint32 { for i := len(stack) - 1; i >= 0; i-- { if stack[i] != errorPassThrough { return stack[i] } } if len(stack) > 0 { return stack[len(stack)-1] } return 0 } ``` *Source: [go/mfilesclient/errors.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/mfilesclient/errors.go)* Handle it like this: ```go _, err := objects.GetObjectInfoAndProperties(ctx, request) if mfilesErr, ok := mfilesclient.AsError(err); ok && mfilesErr.Has(mfilesclient.CodeNotFound) { fmt.Printf("0x%08X: %s\n", mfilesErr.HResult, mfilesErr.Message) } ``` [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md) lists the common codes and what to do about each. **The metadata structure version.** Calls that report the vault's metadata structure version return it in the `mfiles-metadata-version-bin` trailer; `client.MetadataStructureVersion()` has the latest one. See [Metadata structure version and caching](https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/index.md). ## Troubleshooting `go run ./recipes/connect` calls `QueryVersion` before it logs in, so it tells connection problems from login problems. | Symptom | Cause | |---|---| | `generate.ps1`: "No M-Files proto bundle found" | Pass `-ProtoDir` or set `MFILES_PROTO_DIR` to the folder that contains `mfiles/structs.proto`. | | `Unavailable`, "tls: first record does not look like a TLS handshake" | TLS against a server without TLS: set `MFILES_TLS=false`, only for a lab server on a trusted network. | | `Unimplemented`, "unexpected HTTP status code received from server: 404" | A call without TLS to a TLS endpoint, such as M-Files Cloud: remove `MFILES_TLS=false`. | | `Unavailable`, "connection error" ... "Error while dialing" | Wrong host or port, or a firewall in between. | | "M-Files error 0x8004001A (Unknown): Authentication failed." | Wrong user name or password, or the vault uses single sign-on (see the configuration step). | | "M-Files error 0x80040020 (Unknown): The specified document vault does not exist." | Wrong `MFILES_VAULT`. | | "gRPC error Unauthenticated: The current session with M-Files Server is no longer valid." | No session or an expired one: log in again. | ## Next steps * Run the task recipes: `go run ./recipes/`, with `connect`, `vault_structure`, `search`, `read_object`, `download_file`, `create_document`, `update_properties`, `workflow_state`, `paging` or `errors`. The samples' `README.md` lists their arguments; `run-tests.ps1` runs them all. * The recipes explained: [Connect](https://developer.m-files.com/APIs/gRPC-API/guides/connect/index.md), [Vault structure](https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/index.md), [Search](https://developer.m-files.com/APIs/gRPC-API/guides/search/index.md), [Read an object](https://developer.m-files.com/APIs/gRPC-API/guides/read-object/index.md), [Download a file](https://developer.m-files.com/APIs/gRPC-API/guides/download-file/index.md), [Create a document](https://developer.m-files.com/APIs/gRPC-API/guides/create-document/index.md), [Update properties](https://developer.m-files.com/APIs/gRPC-API/guides/update-properties/index.md), [Change the workflow state](https://developer.m-files.com/APIs/gRPC-API/guides/workflow-state/index.md), [Page through results](https://developer.m-files.com/APIs/gRPC-API/guides/paging/index.md) and [Handle errors](https://developer.m-files.com/APIs/gRPC-API/guides/errors/index.md). * Learn the model: [Vault and metadata structure](https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/index.md), [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md), [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md) and [Search](https://developer.m-files.com/APIs/gRPC-API/concepts/search/index.md). * Keep a session healthy in a long-running program: [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). * Find any method in the reference, starting with [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md) and [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md). --- # Connect, log in and keep the session Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/connect/ Do this once at the start of every program: check that the server answers and what it supports, log in, and close the session when you are done. A long-running program also keeps its session alive. ## Calls | Step | Call | Session | |---|---|---| | Server version | [QueryVersion](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCQueryVersion/QueryVersion/index.md) | not needed | | Server features | [GetServerCapabilities](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetServerCapabilities/index.md) | not needed | | Log in to a vault | [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) | creates one | | Session and vault | [GetVaultSessionInfo](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/GetVaultSessionInfo/index.md) | needed | | Keep the session | [KeepAlive](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/KeepAlive/index.md) | needed | | End the session | [LogOut](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogOut/index.md) | needed | * `QueryVersion` returns versions as one 64-bit number each: four 16-bit parts, major in the highest bits. `0x001A000B41050000` is 26.11.16645.0. * `LogIn` binds the session to the vault in `login_data.vault_guid`. The response's `session_data.session_id` (48 bytes) goes into the `mfiles-session-id-bin` metadata of every later call; the helpers do that for you. See [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). * `session_data.keep_alive_interval_in_seconds` is how often to call `KeepAlive` (450 seconds on the server below); the server drops the session after twice that time without one. Other calls do not extend the session. * Many calls return the vault's metadata structure version in the `mfiles-metadata-version-bin` trailer; the helpers keep the latest value. See [Metadata structure version and caching](https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/index.md). * After `LogOut`, calls with the old session ID fail with `UNAUTHENTICATED`. ## Code ### Python ```python # No session is needed for these calls. with MFilesClient.from_env(CLIENT_NAME, log_in=False) as client: version_service = client.stub(IRPCQueryVersion_pb2_grpc.IRPCQueryVersionStub) version = version_service.QueryVersion(IRPCQueryVersion_pb2.QueryVersionRequest()) print(f"Server version: {decode_version(version.server_version)}") login = client.stub(IRPCLogin_pb2_grpc.IRPCLoginStub) capabilities = login.GetServerCapabilities( IRPCLogin_pb2.GetServerCapabilitiesRequest()).server_capabilities print(f"Server capabilities: file_streaming={capabilities.file_streaming}, " f"facet_search={capabilities.facet_search}") session = client.log_in() print(f"Logged in as {session.account_name}; " f"keep-alive interval {session.keep_alive_interval_in_seconds} s") info = login.GetVaultSessionInfo( IRPCLogin_pb2.GetVaultSessionInfoRequest()).vault_session_info print(f"Vault: {info.vault_display_name} {info.vault_guid}") # From the trailers of the calls so far. structure_version = client.metadata_structure_version print(f"Metadata structure version: " f"{'not reported' if structure_version is None else structure_version}") # A long-running program calls KeepAlive within the keep-alive interval. login.KeepAlive(IRPCLogin_pb2.KeepAliveRequest()) print("KeepAlive: ok") client.log_out() print("Logged out") ``` *Source: [python/recipes/connect.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/connect.py)* ### C# ```csharp // No session is needed for these calls. await using var connection = MFilesConnection.Open(settings); var versionService = new IRPCQueryVersion.IRPCQueryVersionClient(connection.CallInvoker); var version = await versionService.QueryVersionAsync(new QueryVersionRequest()); Console.WriteLine($"Server version: {DecodeVersion(version.ServerVersion)}"); var login = new IRPCLogin.IRPCLoginClient(connection.CallInvoker); var capabilities = (await login.GetServerCapabilitiesAsync(new GetServerCapabilitiesRequest())) .ServerCapabilities; Console.WriteLine($"Server capabilities: file_streaming={capabilities.FileStreaming}, " + $"facet_search={capabilities.FacetSearch}"); var session = await connection.LogInAsync(settings, RecipeHelpers.ClientName); Console.WriteLine($"Logged in as {session.AccountName}; " + $"keep-alive interval {session.KeepAliveIntervalInSeconds} s"); var info = (await login.GetVaultSessionInfoAsync(new GetVaultSessionInfoRequest())).VaultSessionInfo; Console.WriteLine($"Vault: {info.VaultDisplayName} {info.VaultGuid}"); Console.WriteLine($"Metadata structure version: {connection.MetadataStructureVersion?.ToString() ?? "not reported"}"); // A long-running program calls KeepAlive within the keep-alive interval. await login.KeepAliveAsync(new KeepAliveRequest()); Console.WriteLine("KeepAlive: ok"); await connection.LogOutAsync(); Console.WriteLine("Logged out"); ``` *Source: [csharp/Recipes/Connect.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/Connect.cs)* ### Go ```go // Dial only creates the channel. Calls without a session work right away. client, err := mfilesclient.Dial(cfg) if err != nil { return err } defer client.Close() version, err := mfiles.NewIRPCQueryVersionClient(client.Conn).QueryVersion(ctx, &mfiles.QueryVersionRequest{}) if err != nil { return err } fmt.Println("Server version:", mfilesclient.VersionString(version.GetServerVersion())) login := mfiles.NewIRPCLoginClient(client.Conn) capabilities, err := login.GetServerCapabilities(ctx, &mfiles.GetServerCapabilitiesRequest{}) if err != nil { return err } fmt.Printf("Server capabilities: file_streaming=%v, facet_search=%v\n", capabilities.GetServerCapabilities().GetFileStreaming(), capabilities.GetServerCapabilities().GetFacetSearch()) // Log in. From now on every call carries the session ID. session, err := client.LogIn(ctx, cfg) if err != nil { return err } fmt.Printf("Logged in as %s; keep-alive interval %d s\n", session.GetAccountName(), session.GetKeepAliveIntervalInSeconds()) info, err := login.GetVaultSessionInfo(ctx, &mfiles.GetVaultSessionInfoRequest{}) if err != nil { return err } fmt.Printf("Vault: %s %s\n", info.GetVaultSessionInfo().GetVaultDisplayName(), info.GetVaultSessionInfo().GetVaultGuid()) // The latest value from the mfiles-metadata-version-bin trailer of the calls so far. if metadataStructureVersion, ok := client.MetadataStructureVersion(); ok { fmt.Println("Metadata structure version:", metadataStructureVersion) } else { fmt.Println("Metadata structure version: not reported") } // Call KeepAlive at least every keep-alive interval on long runs; other calls do not extend // the session. if _, err := login.KeepAlive(ctx, &mfiles.KeepAliveRequest{}); err != nil { return err } fmt.Println("KeepAlive: ok") if err := client.LogOut(ctx); err != nil { return err } fmt.Println("Logged out") ``` *Source: [go/recipes/connect/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/connect/main.go)* Output against an on-premises server: ```text Server version: 26.11.16645.0 Server capabilities: file_streaming=True, facet_search=False Logged in as grpcdocs.user; keep-alive interval 450 s Vault: gRPC Docs Samples {7BBEA0BC-40D8-4BCB-A67D-8A3ACFBDB575} Metadata structure version: 1024 KeepAlive: ok Logged out ``` The two capability flags are examples. `file_streaming` does not mean that the streaming file transfer service can be used; see [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md) for the flags that matter for uploads. ## Find the vault by its name `LogIn` needs the vault's GUID. A program that knows only the vault's name, or wants to let the user pick a vault, finds the GUID first: 1. [LogIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogIn/index.md) with `login_data.vault_guid` set to `{00000000-0000-0000-0000-000000000000}`. This opens a server-level session. 2. [GetVaults](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVaults/GetVaults/index.md) with `exclude_vaults_user_cannot_access` true lists the vaults the user can log in to. Each has `guid` and `display_name`. 3. [LogInWithSession](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCLogin/LogInWithSession/index.md) with the server-level session in `mfiles-session-id-bin` and the chosen vault's GUID in `login_data.vault_guid` returns a vault session, without authenticating again. Then log out the server-level session. Python (the helper `switch_to_vault` does step 3): ```python client.config.vault = SERVER_LEVEL client.log_in() vaults = client.stub(IRPCVaults_pb2_grpc.IRPCVaultsStub).GetVaults( IRPCVaults_pb2.GetVaultsRequest(exclude_vaults_user_cannot_access=True)).vaults for vault in vaults: print(f"{vault.display_name}: {vault.guid}") matches = [v for v in vaults if not name or v.display_name.casefold() == name.casefold()] if not matches: raise SystemExit(f"No vault named '{name}' that this user can access") session = client.switch_to_vault(matches[0].guid) ``` *Source: [python/recipes/find_vault.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/find_vault.py)* ```python response = self.login.LogInWithSession(IRPCLogin_pb2.LogInWithSessionRequest( client_data=self._client_data(), login_data=structs_pb2.LoginData(server_hostname=self.config.host, vault_guid=vault_guid))) self.log_out() # The server-level session; the new vault session stays valid. self.config.vault = vault_guid self.session_id = response.session_data.session_id self.session = response.session_data ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* See [Binding the session to a vault](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md#binding-the-session-to-a-vault). ## Pitfalls * The anonymous calls work without logging in, so they are the right first check of address and TLS settings. Any call that needs a session and has none fails with `UNAUTHENTICATED`, without an M-Files error stack. * `ServerVaultCapabilities` flags are `false` both when a feature is missing and when the server is older than the flag. Check the flag before you use a newer feature. See [Versions and compatibility](https://developer.m-files.com/APIs/gRPC-API/runtime/versioning/index.md). * On M-Files Cloud, keep the `mfilesmsm` cookie and send it back on every call; the helpers do. See [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md#cookies-and-stickiness). * Always log out. A session that is not logged out stays on the server until it times out. ## See also * [Connecting to the server](https://developer.m-files.com/APIs/gRPC-API/runtime/connecting/index.md), [Cloud and on-premises](https://developer.m-files.com/APIs/gRPC-API/runtime/deployments/index.md) * [Call metadata](https://developer.m-files.com/APIs/gRPC-API/runtime/call-metadata/index.md) * Quickstarts: [Python](https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-python/index.md), [C#](https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-csharp/index.md), [Go](https://developer.m-files.com/APIs/gRPC-API/guides/quickstart-go/index.md) --- # Read the vault structure Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/ Most calls take IDs: object type IDs, class IDs, property definition IDs, value list IDs. Apart from the built-in ones, these IDs differ from vault to vault. Read the structure once, cache it, and look up IDs by alias or name instead of hard-coding them. ## Calls | What | Call | Response field | |---|---|---| | Object types | [GetObjectTypes](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectTypes/index.md) | `object_types`: the real object types, not the value lists | | One object type | [GetObjectType](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectTypes/GetObjectType/index.md) | `putf8szobjtypebasic` | | Property definitions | [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md) | `property_defs` | | Classes and class groups | [GetObjectClassesAndGroups](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetObjectClassesAndGroups/index.md) | `classes`, `class_groups` | | ID by alias | [GetMetadataStructureItemIdByAlias](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdByAlias/index.md) | `id`; -1 (-1000 for named ACLs) if no item has the alias | * A class ([ObjectClass](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectClass/index.md)) has its ID and name in `base_info.item_info` (`obj_id.item_id.internal_id`, `name`), its object type in `object_type`, its properties in `associated_property_defs` (with `is_required`) and its default workflow in `default_workflow` (0 for none). * A property definition ([PropertyDef](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyDef/index.md)) has `data_type`, for lookups the value list it refers to in `value_list`, and `is_predefined` true for the built-in ones. * Cache the structure and read it again when the vault's metadata structure version changes; see [Metadata structure version and caching](https://developer.m-files.com/APIs/gRPC-API/concepts/metadata-structure-version-and-caching/index.md). ## Code The recipe lists the object types, counts the property definitions, and lists the document classes with the properties each requires apart from the built-in ones. Then it resolves a class alias and a property alias (defaults `MF.CL.Document` and `MF.PD.Keywords`; pass others as arguments). ### Python ```python # Object types. GetObjectTypes returns the real object types, not the value lists. object_types = client.stub(IRPCObjectTypes_pb2_grpc.IRPCObjectTypesStub) types = object_types.GetObjectTypes( IRPCObjectTypes_pb2.GetObjectTypesRequest()).object_types print(f"Object types: {len(types)}") for object_type in types: print(f" {object_type.id} {object_type.name_plural}") # Property definitions and classes. property_defs = client.stub(IRPCPropertyDefs_pb2_grpc.IRPCPropertyDefsStub) properties = {p.id: p for p in property_defs.GetPropertyDefs( IRPCPropertyDefs_pb2.GetPropertyDefsRequest()).property_defs} print(f"Property definitions: {len(properties)}") classes = property_defs.GetObjectClassesAndGroups( IRPCPropertyDefs_pb2.GetObjectClassesAndGroupsRequest()).classes document_classes = [c for c in classes if c.object_type == 0] print(f"Document classes: {len(document_classes)}") for object_class in document_classes: info = object_class.base_info.item_info # The properties a new object of the class must have, other than built-in ones. required = [properties[p.property_def].name for p in object_class.associated_property_defs if p.is_required and p.property_def in properties and not properties[p.property_def].is_predefined] print(f" {info.obj_id.item_id.internal_id} {info.name}; " f"required: {', '.join(required) or '-'}") ``` *Source: [python/recipes/vault_structure.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/vault_structure.py)* ```python # Code that must work in many vaults refers to classes and properties by alias, not ID. # The answer is -1 when no item, or more than one, has the alias. vault = client.stub(IRPCVault_pb2_grpc.IRPCVaultStub) class_id = vault.GetMetadataStructureItemIdByAlias( IRPCVault_pb2.GetMetadataStructureItemIdByAliasRequest( item_type=enums.METADATA_STRUCTURE_ITEM_OBJECT_CLASS, alias=class_alias)).id property_id = vault.GetMetadataStructureItemIdByAlias( IRPCVault_pb2.GetMetadataStructureItemIdByAliasRequest( item_type=enums.METADATA_STRUCTURE_ITEM_PROPERTY_DEF, alias=property_alias)).id print(f"Class alias {class_alias}: ID {class_id}") print(f"Property alias {property_alias}: ID {property_id}") ``` *Source: [python/recipes/vault_structure.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/vault_structure.py)* ### C# ```csharp // Object types. GetObjectTypes returns the real object types, not the value lists. var objectTypes = new IRPCObjectTypes.IRPCObjectTypesClient(connection.CallInvoker); var types = (await objectTypes.GetObjectTypesAsync(new GetObjectTypesRequest())).ObjectTypes; Console.WriteLine($"Object types: {types.Count}"); foreach (var type in types) Console.WriteLine($" {type.Id} {type.NamePlural}"); // Property definitions and classes. var propertyDefs = new IRPCPropertyDefs.IRPCPropertyDefsClient(connection.CallInvoker); var properties = (await propertyDefs.GetPropertyDefsAsync(new GetPropertyDefsRequest())) .PropertyDefs.ToDictionary(p => p.Id); Console.WriteLine($"Property definitions: {properties.Count}"); var classes = (await propertyDefs.GetObjectClassesAndGroupsAsync(new GetObjectClassesAndGroupsRequest())) .Classes; var documentClasses = classes.Where(c => c.ObjectType == 0).ToList(); Console.WriteLine($"Document classes: {documentClasses.Count}"); foreach (var objectClass in documentClasses) { var info = objectClass.BaseInfo.ItemInfo; // The properties a new object of the class must have, other than built-in ones. var required = objectClass.AssociatedPropertyDefs .Where(p => p.IsRequired) .Select(p => properties.GetValueOrDefault(p.PropertyDef)) .Where(def => def is { IsPredefined: false }) .Select(def => def!.Name); Console.WriteLine($" {info.ObjId.ItemId.InternalId} {info.Name}; " + $"required: {string.Join(", ", required.DefaultIfEmpty("-"))}"); } ``` *Source: [csharp/Recipes/VaultStructure.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/VaultStructure.cs)* ```csharp // Code that must work in many vaults refers to classes and properties by alias, not ID. // The answer is -1 when no item, or more than one, has the alias. var vault = new IRPCVault.IRPCVaultClient(connection.CallInvoker); var classId = await vault.GetMetadataStructureItemIdByAliasAsync( new GetMetadataStructureItemIdByAliasRequest { ItemType = MetadataStructureItem.ObjectClass, Alias = classAlias, }); var propertyId = await vault.GetMetadataStructureItemIdByAliasAsync( new GetMetadataStructureItemIdByAliasRequest { ItemType = MetadataStructureItem.PropertyDef, Alias = propertyAlias, }); Console.WriteLine($"Class alias {classAlias}: ID {classId.Id}"); Console.WriteLine($"Property alias {propertyAlias}: ID {propertyId.Id}"); ``` *Source: [csharp/Recipes/VaultStructure.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/VaultStructure.cs)* ### Go ```go // Object types. GetObjectTypes returns the real object types, not the value lists. types, err := mfiles.NewIRPCObjectTypesClient(client.Conn).GetObjectTypes(ctx, &mfiles.GetObjectTypesRequest{}) if err != nil { return err } fmt.Printf("Object types: %d\n", len(types.GetObjectTypes())) for _, objectType := range types.GetObjectTypes() { fmt.Printf(" %d %s\n", objectType.GetId(), objectType.GetNamePlural()) } // Property definitions and classes. propertyDefs := mfiles.NewIRPCPropertyDefsClient(client.Conn) defs, err := propertyDefs.GetPropertyDefs(ctx, &mfiles.GetPropertyDefsRequest{}) if err != nil { return err } properties := map[int32]*mfiles.PropertyDef{} for _, def := range defs.GetPropertyDefs() { properties[def.GetId()] = def } fmt.Printf("Property definitions: %d\n", len(properties)) classes, err := propertyDefs.GetObjectClassesAndGroups(ctx, &mfiles.GetObjectClassesAndGroupsRequest{}) if err != nil { return err } var documentClasses []*mfiles.ObjectClass for _, class := range classes.GetClasses() { if class.GetObjectType() == 0 { documentClasses = append(documentClasses, class) } } fmt.Printf("Document classes: %d\n", len(documentClasses)) for _, class := range documentClasses { info := class.GetBaseInfo().GetItemInfo() // The properties a new object of the class must have, other than built-in ones. var required []string for _, associated := range class.GetAssociatedPropertyDefs() { def, ok := properties[associated.GetPropertyDef()] if associated.GetIsRequired() && ok && !def.GetIsPredefined() { required = append(required, def.GetName()) } } if len(required) == 0 { required = []string{"-"} } fmt.Printf(" %d %s; required: %s\n", info.GetObjId().GetItemId().GetInternalId(), info.GetName(), strings.Join(required, ", ")) } ``` *Source: [go/recipes/vault_structure/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/vault_structure/main.go)* ```go // Code that must work in many vaults refers to classes and properties by alias, not ID. // The answer is -1 when no item, or more than one, has the alias. vault := mfiles.NewIRPCVaultClient(client.Conn) class, err := vault.GetMetadataStructureItemIdByAlias(ctx, &mfiles.GetMetadataStructureItemIdByAliasRequest{ ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_OBJECT_CLASS, Alias: classAlias, }) if err != nil { return err } property, err := vault.GetMetadataStructureItemIdByAlias(ctx, &mfiles.GetMetadataStructureItemIdByAliasRequest{ ItemType: mfiles.MetadataStructureItem_METADATA_STRUCTURE_ITEM_PROPERTY_DEF, Alias: propertyAlias, }) if err != nil { return err } fmt.Printf("Class alias %s: ID %d\n", classAlias, class.GetId()) fmt.Printf("Property alias %s: ID %d\n", propertyAlias, property.GetId()) ``` *Source: [go/recipes/vault_structure/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/vault_structure/main.go)* Output for a normal user of the M-Files Sample Vault, which defines no aliases: ```text Object types: 11 0 Documents 9 Document collections 10 Assignments 15 19 Shares ... 156 Employees Property definitions: 143 Document classes: 31 0 Document; required: - 1 Other Document; required: - 2 Purchase Invoice; required: Document date 4 Contract or Agreement; required: Agreement title, Agreement type, Owner, Document date 5 Proposal; required: Proposal title, Document date, Proposal number ... Class alias MF.CL.Document: ID -1 Property alias MF.PD.Keywords: ID -1 ``` ## Pitfalls * **An unknown alias is not an error.** `GetMetadataStructureItemIdByAlias` returns -1, or -1000 for a named ACL (-1 is a real named ACL); check for it before using the ID. An empty alias fails with "The parameter is incorrect.". * `item_type` selects the ID space: an alias of a property definition is looked up with `METADATA_STRUCTURE_ITEM_PROPERTY_DEF`, of a class with `METADATA_STRUCTURE_ITEM_OBJECT_CLASS`. * To list all aliases, [GetMetadataStructureItemIdsByAliases](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetMetadataStructureItemIdsByAliases/index.md) and [GetAllMetadataStructureItemIdswithAliases](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCVault/GetAllMetadataStructureItemIdswithAliases/index.md) need `unambiguous_aliases_only` set to true; with false they fail with `E_NOTIMPL` (`0x80004001`). * Administrators can rename items; prefer aliases to names. * Object type names depend on the user: in the Sample Vault, object type 15 ("Reports") comes back with an empty name for a normal user (see the output above). * `GetObjectTypes` returns only real object types (11 in the Sample Vault, for a normal user and for an administrator alike); value lists share their ID space but are not in that list. * The response field of `GetObjectType` is named `putf8szobjtypebasic`. ## See also * [Vault and metadata structure](https://developer.m-files.com/APIs/gRPC-API/concepts/vault-and-metadata-structure/index.md): the ID spaces and the built-in IDs. * [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md) --- # Search for documents Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/search/ Search is how you find objects in M-Files: there are no folder paths to walk. This recipe finds the documents of one class modified in the last N days, newest first, and stops after a limit. ## Calls [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md) with: | Field | Value | |---|---| | `conditions` | One [SearchConditionArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchConditionArray/index.md) with the four conditions below; the conditions inside it are ANDed (SearchObjects takes exactly one array) | | `sorting` | One [SortRule](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SortRule/index.md): `key` 21 (Last modified), `options.sort_descending` true | | `limit` | The maximum number of results | | Condition | `expression` | `type` | `value` | |---|---|---|---| | Object type is Document | `status_value`, `STATUS_TYPE_OBJECT_TYPE` | `CONDITION_TYPE_EQUAL` | lookup: value list -11, item 0 | | Not deleted | `status_value`, `STATUS_TYPE_IS_DELETED` | `CONDITION_TYPE_EQUAL` | boolean false | | Class | `property_value`, `property_def` 100 | `CONDITION_TYPE_EQUAL` | lookup: value list 1, item = class ID | | Modified since | `property_value`, `property_def` 21 | `CONDITION_TYPE_GREATER_THAN_OR_EQUAL` | timestamp | Each result is a [SearchResultsItem](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchResultsItem/index.md); `object.version_info` has the title, version and `last_modified_at_utc`, `object.object_info.obj_id` the object's ID. `has_more_results` is true when the limit cut the results short. ## Code The condition and value builders (`is_object_type`, `is_not_deleted`, `property_condition`, `lookup`, `timestamp` and their C# and Go counterparts) are small functions of the samples' helpers (`mfiles_values.py`, `MFilesValues.cs`, `mfilesclient`); each builds one [SearchCondition](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SearchCondition/index.md) or [TypedValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValue/index.md) as in the tables above. ### Python ```python since = datetime.now(timezone.utc) - timedelta(days=days) request = IRPCSearch_pb2.SearchObjectsRequest( # One array: all its conditions must match. conditions=[structs.SearchConditionArray(value=[ is_object_type(0), # documents is_not_deleted(), # Property 100 "Class" is a lookup into value list 1, the classes. property_condition(100, enums.CONDITION_TYPE_EQUAL, lookup(1, class_id)), # Property 21 "Last modified" is a timestamp. property_condition(21, enums.CONDITION_TYPE_GREATER_THAN_OR_EQUAL, timestamp(since)), ])], # Newest first. The server sorts all matches, then returns the first `limit`. sorting=structs.SearchResultsSorting(rules=structs.SortRuleValueArray(values=[ structs.SortRule(key=21, options=enums.SortRuleFlags(sort_descending=True))])), limit=limit) search = client.stub(IRPCSearch_pb2_grpc.IRPCSearchStub) response = search.SearchObjects(request) ``` *Source: [python/recipes/search.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/search.py)* ### C# ```csharp var since = DateTime.UtcNow.AddDays(-days); var request = new SearchObjectsRequest { // One array: all its conditions must match. Conditions = { new SearchConditionArray { Value = { IsObjectType(0), // documents IsNotDeleted(), // Property 100 "Class" is a lookup into value list 1, the classes. PropertyCondition(100, ConditionType.Equal, Lookup(1, classId)), // Property 21 "Last modified" is a timestamp. PropertyCondition(21, ConditionType.GreaterThanOrEqual, Timestamp(since)), }, }, }, // Newest first. The server sorts all matches, then returns the first `limit`. Sorting = new SearchResultsSorting { Rules = new SortRuleValueArray { Values = { new SortRule { Key = 21, Options = new SortRuleFlags { SortDescending = true } } }, }, }, Limit = (uint)limit, }; var search = new IRPCSearch.IRPCSearchClient(connection.CallInvoker); var response = await search.SearchObjectsAsync(request); ``` *Source: [csharp/Recipes/Search.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/Search.cs)* ### Go ```go since := time.Now().UTC().AddDate(0, 0, -int(days)) request := &mfiles.SearchObjectsRequest{ // One array: all its conditions must match. Conditions: mfilesclient.AllOf( mfilesclient.IsObjectType(0), // documents mfilesclient.IsNotDeleted(), // Property 100 "Class" is a lookup into value list 1, the classes. mfilesclient.PropertyCondition(100, mfiles.ConditionType_CONDITION_TYPE_EQUAL, mfilesclient.Lookup(1, classID)), // Property 21 "Last modified" is a timestamp. mfilesclient.PropertyCondition(21, mfiles.ConditionType_CONDITION_TYPE_GREATER_THAN_OR_EQUAL, mfilesclient.Timestamp(since)), ), // Newest first. The server sorts all matches, then returns the first `limit`. Sorting: mfilesclient.SortBy(21, true), Limit: uint32(limit), } search := mfiles.NewIRPCSearchClient(client.Conn) response, err := search.SearchObjects(ctx, request) if err != nil { return err } ``` *Source: [go/recipes/search/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/search/main.go)* Output with the defaults (class 1, "Other Document" in the Sample Vault; 30 days) and limit 2: ```text Documents of class 1 modified since 2026-10-01: 2 (limited to 2; more exist) [docs-test] Created by the Python sample 2026-10-31 15:06:31 (ID 629, version 1, modified 2026-10-31 15:06:31 UTC) [docs-test] Created by the Python sample 2026-10-31 15:06:29 (ID 628, version 1, modified 2026-10-31 15:06:29 UTC) ``` The recipe's arguments are the class ID (default 1), the number of days (default 30) and the limit (default 10). ## Pitfalls * **Deleted objects.** A user who can see deleted objects, such as an administrator, gets them in the results unless a condition excludes them. Add `STATUS_TYPE_IS_DELETED` equal to false to every search that should find only live objects. * **One sort rule.** The server sorts by one rule at most. The property must be searchable and not multi-line text; otherwise the search fails, or falls back to the default order when `sorting.options.allow_resorting_to_default_sort` is true. * **Enum flag messages.** `SortRuleFlags` and `SearchFlags` are messages with one boolean per flag, defined in `enums.proto` (in Python: `enums_pb2.SortRuleFlags`). * **Typed values must match the property's data type.** A timestamp property takes a `timestamp` value; a lookup property takes a `lookup` whose `value_list_item_info.obj_id` names the value list (`type`) and the item (`item_id.internal_id`). * **IDs are vault-specific** except the built-in ones (property 21 Last modified, 100 Class; object type 0 Document). Resolve the others by alias: see [Read the vault structure](https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/index.md). * An object type condition with a plain integer value fails with `0x80040027` "Invalid search condition."; use a lookup. ## See also * [Search](https://developer.m-files.com/APIs/gRPC-API/concepts/search/index.md): the condition model and more condition shapes. * [Page through results](https://developer.m-files.com/APIs/gRPC-API/guides/paging/index.md) for result sets larger than one call should return. --- # Read an object and its properties Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/read-object/ Given an object's type and ID (for example from a search), get its latest version, its files and all its property values, and turn the typed values into text. ## Calls [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md) with: | Field | Value | |---|---| | `obj_id` | The object: `type` is the object type ID, `item_id.internal_id` the object ID | | `latest_version` | true for the latest version; false to read the version in `doc_version` | | `allow_checked_out` | Optional: true to get your own checked-out version when the object is checked out to you (the recipe leaves it false) | The response has `object_info` ([ObjectVersionEx](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectVersionEx/index.md): title, version, files, check-out state) and `property_array`, a list of [PropertyValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.PropertyValue/index.md). A property value carries only the property definition ID; get the names from [GetPropertyDefs](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCPropertyDefs/GetPropertyDefs/index.md) (and cache them). Each value is a [TypedValue](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.TypedValue/index.md): `type` is the data type, and one field of the `data` oneof holds the value: | Data type | Field of `data` | Notes | |---|---|---| | `DATATYPE_TEXT`, `DATATYPE_MULTI_LINE_TEXT` | `text`, `multi_line_text` | | | `DATATYPE_INTEGER`, `DATATYPE_INTEGER64`, `DATATYPE_REAL_NUMBER` | `integer`, `integer64`, `real_number` | | | `DATATYPE_BOOLEAN` | `boolean` | | | `DATATYPE_DATE` | `date` | A `Timestamp` at midnight UTC of the date | | `DATATYPE_TIMESTAMP` | `timestamp` | UTC | | `DATATYPE_LOOKUP` | `lookup` | `value_list_item_info.obj_id` (value list and item ID) and `.name` | | `DATATYPE_MULTI_SELECT_LOOKUP` | `multi_select_lookup.values` | A list of lookups | An empty value has `is_null_value` true and no `data` field set. ## Code The recipe reads object 437 of object type 0 by default; pass another object ID and object type as arguments. `format_value` (`FormatValue` in C# and Go) turns a typed value into text: it checks `is_null_value`, then which member of the `data` oneof is set. ### Python ```python def format_value(value: structs.TypedValue) -> str: """A typed value as text: lookups show their names and IDs.""" if value.is_null_value: return "(empty)" data = value.data kind = data.WhichOneof("data") # The member of the oneof that is set. if kind in ("text", "multi_line_text", "integer", "integer64", "real_number", "decimal_number"): return str(getattr(data, kind)) if kind == "boolean": return "yes" if data.boolean else "no" if kind == "date": # Midnight UTC of the day. return f"{data.date.ToDatetime():%Y-%m-%d}" if kind == "timestamp": return f"{data.timestamp.ToDatetime():%Y-%m-%d %H:%M:%S} UTC" if kind == "lookup": return _format_lookup(data.lookup) if kind == "multi_select_lookup": return "; ".join(_format_lookup(item) for item in data.multi_select_lookup.values) return f"({value.type})" def _format_lookup(item: structs.Lookup) -> str: info = item.value_list_item_info return f"{info.name} (ID {info.obj_id.item_id.internal_id})" ``` *Source: [python/recipes/recipe_helpers.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/recipe_helpers.py)* ```python # Property names, by ID. A real application caches these (they change rarely). property_defs = client.stub(IRPCPropertyDefs_pb2_grpc.IRPCPropertyDefsStub) names = {p.id: p.name for p in property_defs.GetPropertyDefs( IRPCPropertyDefs_pb2.GetPropertyDefsRequest()).property_defs} # The latest version and its properties in one call. objects = client.stub(IRPCObjectOperations_pb2_grpc.IRPCObjectOperationsStub) response = objects.GetObjectInfoAndProperties( IRPCObjectOperations_pb2.GetObjectInfoAndPropertiesRequest( obj_id=obj_id(object_type, item_id), latest_version=True)) print(describe(response.object_info)) for prop in response.property_array: name = names.get(prop.property_def, "?") print(f" {name} ({prop.property_def}): {format_value(prop.value)}") ``` *Source: [python/recipes/read_object.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/read_object.py)* ### C# ```csharp /// A typed value as text: lookups show their names and IDs. public static string FormatValue(TypedValue value) { if (value.IsNullValue) return "(empty)"; var data = value.Data; return data.DataCase switch { TypedValueUnion.DataOneofCase.Text => data.Text, TypedValueUnion.DataOneofCase.MultiLineText => data.MultiLineText, TypedValueUnion.DataOneofCase.Integer => data.Integer.ToString(CultureInfo.InvariantCulture), TypedValueUnion.DataOneofCase.Integer64 => data.Integer64.ToString(CultureInfo.InvariantCulture), TypedValueUnion.DataOneofCase.RealNumber => data.RealNumber.ToString(CultureInfo.InvariantCulture), TypedValueUnion.DataOneofCase.DecimalNumber => data.DecimalNumber, TypedValueUnion.DataOneofCase.Boolean => data.Boolean ? "yes" : "no", // A date is midnight UTC of the day. TypedValueUnion.DataOneofCase.Date => data.Date.ToDateTime().ToString("yyyy-MM-dd"), TypedValueUnion.DataOneofCase.Timestamp => data.Timestamp.ToDateTime().ToString("yyyy-MM-dd HH:mm:ss") + " UTC", TypedValueUnion.DataOneofCase.Lookup => FormatLookup(data.Lookup), TypedValueUnion.DataOneofCase.MultiSelectLookup => string.Join("; ", data.MultiSelectLookup.Values.Select(FormatLookup)), _ => $"({value.Type})", }; } private static string FormatLookup(Lookup lookup) => $"{lookup.ValueListItemInfo.Name} (ID {lookup.ValueListItemInfo.ObjId.ItemId.InternalId})"; ``` *Source: [csharp/Recipes/RecipeHelpers.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/RecipeHelpers.cs)* ```csharp // Property names, by ID. A real application caches these (they change rarely). var propertyDefs = new IRPCPropertyDefs.IRPCPropertyDefsClient(connection.CallInvoker); var names = (await propertyDefs.GetPropertyDefsAsync(new GetPropertyDefsRequest())) .PropertyDefs.ToDictionary(p => p.Id, p => p.Name); // The latest version and its properties in one call. var objects = new IRPCObjectOperations.IRPCObjectOperationsClient(connection.CallInvoker); var response = await objects.GetObjectInfoAndPropertiesAsync(new GetObjectInfoAndPropertiesRequest { ObjId = ObjId(objectType, id), LatestVersion = true, }); Console.WriteLine(RecipeHelpers.Describe(response.ObjectInfo)); foreach (var property in response.PropertyArray) { string name = names.GetValueOrDefault(property.PropertyDef, "?"); Console.WriteLine($" {name} ({property.PropertyDef}): {RecipeHelpers.FormatValue(property.Value)}"); } ``` *Source: [csharp/Recipes/ReadObject.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/ReadObject.cs)* ### Go ```go // FormatValue prints a typed value; lookups show their names and IDs. The value's Data holds // one member of the TypedValueUnion oneof, as one of the TypedValueUnion_* wrapper types. func FormatValue(value *mfiles.TypedValue) string { if value.GetIsNullValue() { return "(empty)" } switch data := value.GetData().GetData().(type) { case *mfiles.TypedValueUnion_Text: return data.Text case *mfiles.TypedValueUnion_MultiLineText: return data.MultiLineText case *mfiles.TypedValueUnion_Integer: return strconv.Itoa(int(data.Integer)) case *mfiles.TypedValueUnion_Integer64: return strconv.FormatInt(data.Integer64, 10) case *mfiles.TypedValueUnion_RealNumber: return strconv.FormatFloat(data.RealNumber, 'g', -1, 64) case *mfiles.TypedValueUnion_DecimalNumber: return data.DecimalNumber case *mfiles.TypedValueUnion_Boolean: if data.Boolean { return "yes" } return "no" case *mfiles.TypedValueUnion_Date: // midnight UTC of the day return data.Date.AsTime().Format(time.DateOnly) case *mfiles.TypedValueUnion_Timestamp: return data.Timestamp.AsTime().Format(time.DateTime) + " UTC" case *mfiles.TypedValueUnion_Lookup: return formatLookup(data.Lookup) case *mfiles.TypedValueUnion_MultiSelectLookup: items := make([]string, 0, len(data.MultiSelectLookup.GetValues())) for _, lookup := range data.MultiSelectLookup.GetValues() { items = append(items, formatLookup(lookup)) } return strings.Join(items, "; ") default: return fmt.Sprintf("(%s)", value.GetType()) } } func formatLookup(lookup *mfiles.Lookup) string { item := lookup.GetValueListItemInfo() return fmt.Sprintf("%s (ID %d)", item.GetName(), item.GetObjId().GetItemId().GetInternalId()) } ``` *Source: [go/recipes/internal/recipe/recipe.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/internal/recipe/recipe.go)* ```go // Property names, by ID. A real application caches these (they change rarely). propertyDefs := mfiles.NewIRPCPropertyDefsClient(client.Conn) defs, err := propertyDefs.GetPropertyDefs(ctx, &mfiles.GetPropertyDefsRequest{}) if err != nil { return err } names := map[int32]string{} for _, def := range defs.GetPropertyDefs() { names[def.GetId()] = def.GetName() } // The latest version and its properties in one call. objects := mfiles.NewIRPCObjectOperationsClient(client.Conn) response, err := objects.GetObjectInfoAndProperties(ctx, &mfiles.GetObjectInfoAndPropertiesRequest{ ObjId: mfilesclient.ObjID(objectType, id), LatestVersion: true, }) if err != nil { return err } fmt.Println(recipe.Describe(response.GetObjectInfo())) for _, property := range response.GetPropertyArray() { name, ok := names[property.GetPropertyDef()] if !ok { name = "?" } fmt.Printf(" %s (%d): %s\n", name, property.GetPropertyDef(), recipe.FormatValue(property.GetValue())) } ``` *Source: [go/recipes/read_object/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/read_object/main.go)* Output for a document of the Sample Vault: ```text Proposal 7732 - ESTT Corporation (IT) (ID 437, version 1, modified 2012-06-07 10:39:43 UTC) Name or title (0): Proposal 7732 - ESTT Corporation (IT) Last modified (21): 2012-06-07 10:39:43 UTC Single file (22): yes Last modified by (23): Andy Nash (ID 21) Status changed (24): 2012-06-07 10:39:43 UTC Size on server (this version) (30): 57344 Class (100): Proposal (ID 5) Class groups (101): 1. PURCHASING AND SALES (ID 3) Document date (1002): 2005-03-02 Keywords (1004): (empty) Description (1034): (empty) Document (1076): Order - Graphical Design (ID 293) Project (1078): Redesign of ESTT Marketing Material (ID 34) Customer (1079): ESTT Corporation (IT) (ID 141) Effective through (1096): 2007-08-01 Accepted (1098): yes Proposal title (1102): Proposal 7732 - ESTT Corporation (IT) Proposal number (1103): 7732 Created (20): 2012-06-07 10:39:31 UTC Created by (25): Andy Nash (ID 21) Size on server (all versions) (31): 57344 Marked for archiving (32): no Object changed (89): 2012-06-07 10:39:43 UTC ``` ## Pitfalls * **Check the oneof, not the type.** Read the value from the field that `data` actually has set (`WhichOneof` in Python), and check `is_null_value` first. * **Lookup items can be in any value list.** The `obj_id.type` of a lookup is the value list or object type; in proto3 a 0 (the Document type) is not sent, so an unset `type` means 0. * **Dates have no time zone.** A `DATATYPE_DATE` value is a date; print it in UTC, or it can shift by a day. * **Not found.** An object that does not exist, or that the user cannot see, fails with `0x8004000B` "Not found." See [Handle errors](https://developer.m-files.com/APIs/gRPC-API/guides/errors/index.md). * The property list can contain properties that the object's class does not list (1076 above), and it is not sorted by property ID. ## See also * [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md) * [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md) * [Download a file](https://developer.m-files.com/APIs/gRPC-API/guides/download-file/index.md) --- # Download a file Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/download-file/ The files of an object version are listed in its version information. To get the content, open a download session for one file version, read it in blocks and close the session. ## Calls 1. [GetObjectInfoAndProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetObjectInfoAndProperties/index.md) (or a search result): `object_info.version_info.files` lists the [File](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.File/index.md)s with `title`, `extension`, `size` and `file_ver`. 2. [DownloadFile_Begin](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Begin/index.md) with `obj_file_ver` (the object's `obj_id` and the file's `file_ver`), `format` `FILE_FORMAT_NATIVE` and `prepare_file_for_downloading` true. It returns `download_id` and `file_outline.size`. 3. [DownloadFile_ReadBlock](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_ReadBlock/index.md) with `download_id`, `offset` and `block_size` until you have `size` bytes. The samples ask for 4 MiB, or for the rest of the file if that is less. 4. [DownloadFile_Stop](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DownloadFile_Stop/index.md) with `download_id`, also after an error. The recipe downloads every file of the latest version of a document (default ID 437) into a folder (default `mfiles-grpc-samples` in the temporary folder). ## Code The function that downloads one file version: ### Python ```python def download(objects, object_id: structs.ObjID, file_ver: structs.FileVer, path: Path) -> int: """Downloads one file version in blocks: DownloadFile_Begin opens a download session, DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it.""" begin = objects.DownloadFile_Begin(ops.DownloadFile_BeginRequest( obj_file_ver=structs.ObjFileVer(obj_id=object_id, file_ver=file_ver), format=enums.FILE_FORMAT_NATIVE, prepare_file_for_downloading=True)) try: size = begin.file_outline.size with open(path, "wb") as output: offset = 0 while offset < size: block = objects.DownloadFile_ReadBlock(ops.DownloadFile_ReadBlockRequest( download_id=begin.download_id, block_size=min(BLOCK_SIZE, size - offset), offset=offset)) if not block.data: raise IOError(f"The server returned no data at offset {offset} of {size}.") output.write(block.data) offset += len(block.data) return size finally: objects.DownloadFile_Stop(ops.DownloadFile_StopRequest(download_id=begin.download_id)) ``` *Source: [python/recipes/download_file.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/download_file.py)* ### C# ```csharp /// /// Downloads one file version in blocks: DownloadFile_Begin opens a download session, /// DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it. /// private static async Task DownloadAsync(IRPCObjectOperations.IRPCObjectOperationsClient objects, ObjID objId, FileVer fileVer, string path) { var begin = await objects.DownloadFile_BeginAsync(new DownloadFile_BeginRequest { ObjFileVer = new ObjFileVer { ObjId = objId, FileVer = fileVer }, Format = FileFormat.Native, PrepareFileForDownloading = true, }); try { await using var output = System.IO.File.Create(path); long size = begin.FileOutline.Size; for (long offset = 0; offset < size;) { var block = await objects.DownloadFile_ReadBlockAsync(new DownloadFile_ReadBlockRequest { DownloadId = begin.DownloadId, BlockSize = (uint)Math.Min(BlockSize, size - offset), Offset = offset, }); if (block.Data.IsEmpty) throw new IOException($"The server returned no data at offset {offset} of {size}."); block.Data.WriteTo(output); offset += block.Data.Length; } return size; } finally { await objects.DownloadFile_StopAsync(new DownloadFile_StopRequest { DownloadId = begin.DownloadId }); } } ``` *Source: [csharp/Recipes/DownloadFile.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/DownloadFile.cs)* ### Go ```go // download saves one file version in blocks: DownloadFile_Begin opens a download session, // DownloadFile_ReadBlock reads it, DownloadFile_Stop closes it. func download(ctx context.Context, objects mfiles.IRPCObjectOperationsClient, objID *mfiles.ObjID, fileVer *mfiles.FileVer, path string) (int64, error) { begin, err := objects.DownloadFile_Begin(ctx, &mfiles.DownloadFile_BeginRequest{ ObjFileVer: &mfiles.ObjFileVer{ObjId: objID, FileVer: fileVer}, Format: mfiles.FileFormat_FILE_FORMAT_NATIVE, PrepareFileForDownloading: true, }) if err != nil { return 0, err } downloadID := begin.GetDownloadId() defer objects.DownloadFile_Stop(context.WithoutCancel(ctx), &mfiles.DownloadFile_StopRequest{DownloadId: downloadID}) output, err := os.Create(path) if err != nil { return 0, err } defer output.Close() size := begin.GetFileOutline().GetSize() for offset := int64(0); offset < size; { block, err := objects.DownloadFile_ReadBlock(ctx, &mfiles.DownloadFile_ReadBlockRequest{ DownloadId: downloadID, BlockSize: uint32(min(blockSize, size-offset)), Offset: offset, }) if err != nil { return 0, err } if len(block.GetData()) == 0 { return 0, fmt.Errorf("the server returned no data at offset %d of %d", offset, size) } if _, err := output.Write(block.GetData()); err != nil { return 0, err } offset += int64(len(block.GetData())) } return size, output.Close() } ``` *Source: [go/recipes/download_file/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/download_file/main.go)* Output: ```text Proposal 7732 - ESTT Corporation (IT) (ID 437, version 1, modified 2012-06-07 10:39:43 UTC) Downloaded Proposal 7732 - ESTT Corporation (IT).doc (57344 bytes) to C:\Users\me\AppData\Local\Temp\mfiles-grpc-samples\Proposal 7732 - ESTT Corporation (IT).doc ``` ## Pitfalls * **`prepare_file_for_downloading` must be true.** Without it, `DownloadFile_Begin` only checks access and returns `download_id` 0, and `DownloadFile_ReadBlock` then fails with `0x8004000B` "Not found. (The specified download session 0 does not exist.)". * **At most 4 MiB per block.** A larger `block_size` fails with `E_INVALIDARG` (`0x80070057`, "Block size exceeded the limit of 4194304 bytes."). The samples read blocks of at most 4 MiB; their helpers accept responses of any size, so the 4 MiB default of gRPC libraries does not get in the way. See [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md). * **The session can end with the last block.** After a read that reaches the end of the file with a block larger than the rest, the session may already be gone. `DownloadFile_Stop` succeeds on a closed session too, so always call it, also after errors. * **The session lives on one server instance.** On M-Files Cloud, keep the `mfilesmsm` cookie so that every block goes to the instance that holds the session; the helpers do. * **File names.** `title` can contain characters that are not allowed in file names, such as `/`. Replace them before you write the file, as the recipes do. * A document can have no files, or several (when the property Single file, 22, is false). ## See also * [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md): download and upload in detail, formats and limits. * [Create a document](https://developer.m-files.com/APIs/gRPC-API/guides/create-document/index.md) for the upload direction. --- # Create a document Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/create-document/ Upload the file content to a temporary upload on the server first, then create the document in one call that names the upload, the properties and the permissions, and checks the new document in. ## Calls 1. [UploadTempFile](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFile/index.md): send the content in blocks. The first call has `upload_id` 0, `offset` 0 and the file's `total_size_in_bytes`, and returns the `upload_id` of a new upload; later calls pass that ID and the `offset` of their block. The samples send blocks of 4 MiB. 2. [UploadTempFileCommit](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFileCommit/index.md) with `upload_id` and `logical_size`, the number of bytes uploaded. An upload that is not committed cannot be used. 3. [AddObjectWithFiles](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/AddObjectWithFiles/index.md) with: | Field | Value | |---|---| | `object_type_id` | 0 (Document) | | `properties` | 0 Name or title (text), 100 Class (lookup, value list 1), 22 Single file (boolean true), and the properties the class requires | | `acl` | `custom_component` with `has_named_acl` true and `named_acl_id` = the named ACL to use; the samples use -1 | | `files` | `type` `OBJ_FILE_SOURCE_TYPE_UPLOADED_FILE`, `data.uploaded_file` with `upload_id`, `title`, `extension` (no dot) and `size` | | `check_in` | true; false leaves the new document checked out to you | `AddObjectWithFiles` consumes the upload. The response's `created_object.object_version` has the new object's `object_info.obj_id` and the version information with the file. ## Permissions **Always send `acl`.** An object created without one gets an empty access control list: nobody can see it, not even the user who created it, and the call still succeeds. The samples link the new document to a named ACL with `has_named_acl: true` and `named_acl_id: -1`. In a default vault, -1 is "Full control for all internal users". Pick the named ACL that your application needs; the IDs of the others differ from vault to vault. See [Permissions](https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/index.md). ## Code ### Python The upload: ```python # Upload the content to a temporary file on the server, in blocks. The first call # (upload_id 0) starts the upload; the next ones continue it. upload_id = 0 for offset in range(0, max(len(content), 1), BLOCK_SIZE): upload_id = objects.UploadTempFile(ops.UploadTempFileRequest( upload_id=upload_id, total_size_in_bytes=len(content), offset=offset, data=content[offset:offset + BLOCK_SIZE])).upload_id objects.UploadTempFileCommit(ops.UploadTempFileCommitRequest( upload_id=upload_id, logical_size=len(content))) ``` *Source: [python/recipes/create_document.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/create_document.py)* The document: ```python response = objects.AddObjectWithFiles(ops.AddObjectWithFilesRequest( object_type_id=0, # Document properties=[ property_value(0, text(title)), # Name or title property_value(100, lookup(1, 1)), # Class: value list 1, item 1 "Other Document" property_value(22, boolean(True)), # Single file ], # Always send an ACL: without one, the new document has no permissions and not even # its creator can see it. Named ACL -1 is "Full control for all internal users". acl=named_acl(-1), files=structs.ObjFileSource( type=enums.OBJ_FILE_SOURCE_TYPE_UPLOADED_FILE, data=structs.ObjFileSourceUnion( uploaded_file=structs.ObjFileSourceUnion.UploadedFileData( upload_id=upload_id, title=title, # a single-file document's file takes the document's title extension="txt", size=len(content)))), check_in=True)) created = response.created_object.object_version print(f"Created {describe(created)}") ``` *Source: [python/recipes/create_document.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/create_document.py)* ### C# The upload: ```csharp // Upload the content to a temporary file on the server, in blocks. The first call // (upload_id 0) starts the upload; the next ones continue it. int uploadId = 0; for (int offset = 0; offset == 0 || offset < content.Length; offset += BlockSize) { var block = await objects.UploadTempFileAsync(new UploadTempFileRequest { UploadId = uploadId, TotalSizeInBytes = content.Length, Offset = offset, Data = ByteString.CopyFrom(content, offset, Math.Min(BlockSize, content.Length - offset)), }); uploadId = block.UploadId; } await objects.UploadTempFileCommitAsync(new UploadTempFileCommitRequest { UploadId = uploadId, LogicalSize = content.Length, }); ``` *Source: [csharp/Recipes/CreateDocument.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/CreateDocument.cs)* The document: ```csharp var response = await objects.AddObjectWithFilesAsync(new AddObjectWithFilesRequest { ObjectTypeId = 0, // Document Properties = { Property(0, Text(title)), // Name or title Property(100, Lookup(1, 1)), // Class: value list 1, item 1 "Other Document" Property(22, Boolean(true)), // Single file }, // Always send an ACL: without one, the new document has no permissions and not even // its creator can see it. Named ACL -1 is "Full control for all internal users". Acl = new AccessControlList { CustomComponent = new AccessControlListComponent { HasNamedAcl = true, NamedAclId = -1 }, }, Files = new ObjFileSource { Type = ObjFileSourceType.UploadedFile, Data = new ObjFileSourceUnion { UploadedFile = new ObjFileSourceUnion.Types.UploadedFileData { UploadId = uploadId, Title = title, // a single-file document's file takes the document's title Extension = "txt", Size = content.Length, }, }, }, CheckIn = true, }); var created = response.CreatedObject.ObjectVersion; Console.WriteLine($"Created {RecipeHelpers.Describe(created)}"); ``` *Source: [csharp/Recipes/CreateDocument.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/CreateDocument.cs)* ### Go The upload: ```go // upload uploads content to a temporary file on the server, in blocks, and returns its upload // ID. The first call (upload_id 0) starts the upload; the next ones continue it. func upload(ctx context.Context, objects mfiles.IRPCObjectOperationsClient, content []byte) (int32, error) { var uploadID int32 for offset := 0; offset == 0 || offset < len(content); offset += blockSize { block, err := objects.UploadTempFile(ctx, &mfiles.UploadTempFileRequest{ UploadId: uploadID, TotalSizeInBytes: int64(len(content)), Offset: int64(offset), Data: content[offset:min(offset+blockSize, len(content))], }) if err != nil { return 0, err } uploadID = block.GetUploadId() } _, err := objects.UploadTempFileCommit(ctx, &mfiles.UploadTempFileCommitRequest{ UploadId: uploadID, LogicalSize: int64(len(content)), }) return uploadID, err } ``` *Source: [go/recipes/create_document/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/create_document/main.go)* The document: ```go response, err := objects.AddObjectWithFiles(ctx, &mfiles.AddObjectWithFilesRequest{ ObjectTypeId: 0, // Document Properties: []*mfiles.PropertyValue{ mfilesclient.PropertyValue(0, mfilesclient.Text(title)), // Name or title mfilesclient.PropertyValue(100, mfilesclient.Lookup(1, 1)), // Class: "Other Document" mfilesclient.PropertyValue(22, mfilesclient.Boolean(true)), // Single file }, // Always send an ACL: without one, the new document has no permissions and not even its // creator can see it. Named ACL -1 is "Full control for all internal users". Acl: mfilesclient.NamedACL(mfilesclient.NamedACLFullControlForAllInternalUsers), Files: &mfiles.ObjFileSource{ Type: mfiles.ObjFileSourceType_OBJ_FILE_SOURCE_TYPE_UPLOADED_FILE, Data: &mfiles.ObjFileSourceUnion{Data: &mfiles.ObjFileSourceUnion_UploadedFile{ UploadedFile: &mfiles.ObjFileSourceUnion_UploadedFileData{ UploadId: uploadID, Title: title, // a single-file document's file takes the document's title Extension: "txt", Size: int64(len(content)), }, }}, }, CheckIn: true, }) if err != nil { return err } created := response.GetCreatedObject().GetObjectVersion() fmt.Println("Created", recipe.Describe(created)) ``` *Source: [go/recipes/create_document/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/create_document/main.go)* Output (the recipe deletes the document at the end unless you pass `--keep`): ```text Created [docs-test] Created by the Python sample 2026-10-31 14:55:56 (ID 611, version 1, modified 2026-10-31 14:55:56 UTC) File: [docs-test] Created by the Python sample 2026-10-31 14:55:56.txt, 96 bytes Deleted object 0-611 ``` ## Pitfalls * **Required properties.** The built-in class Document (0) requires Name or title (0) and Single file (22); many classes require more. A class lists them in `associated_property_defs` with `is_required`: see [Read the vault structure](https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/index.md). * **Single file** is true for a document with exactly one file and false otherwise. The other recipes create documents without files, with Single file false and no `files`. * **The file takes the document's title** in a single-file document. The samples pass the document's title as the file's `title`. * **Commit exactly once, with the right size.** A `logical_size` that differs from the bytes uploaded, or a second commit, fails with "File size mismatch." (`0x8004003D`) and removes the upload. * **Uploads belong to the login session** that made them, on the server instance that received the first block; keep the `mfilesmsm` cookie on M-Files Cloud (the helpers do). An unused upload is removed after a while; see [UploadTempFile](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UploadTempFile/index.md). * **Cleaning up.** The recipes delete what they create with [RemoveObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/RemoveObject/index.md). Destroying with [DestroyObject](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/DestroyObject/index.md) needs a vault-level right that normal users do not have; they get `0x800400A9`. ## See also * [Files](https://developer.m-files.com/APIs/gRPC-API/runtime/files/index.md): uploads and downloads in detail. * [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md) * [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md) * [Update properties](https://developer.m-files.com/APIs/gRPC-API/guides/update-properties/index.md) --- # Update properties safely Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/update-properties/ Every change to an object's properties creates a new version. Base the change on the version you read, so that the server refuses it if someone else changed the object in between. There are two ways: * **A. One call.** [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) on a checked-in object: the server checks the object out, sets the properties and checks it in. * **B. Check out, change, check in.** [CheckOut](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckOut/index.md), any number of `SetProperties` calls on the checked-out version, then [CheckIn](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/CheckIn/index.md). All changes become one new version. ## Calls `SetProperties` fields: | Field | A. checked-in object | B. checked-out object | |---|---|---| | `obj_ver` | The object and the version you read | The object and the checked-out version from `CheckOut` | | `allow_modifying_checked_in_object` | true | unset | | `fail_if_newer_version_exists` | true: refuse if a newer version exists | unset | | `properties` | The values to add or replace | The values to add or replace | | `remove_unspecified_properties` | false: keep all other properties | false | * `CheckOut` takes the `obj_id`; the checked-out version is `object_info.object_version.version_info.version` of its response. * `CheckIn` takes the `obj_ver` of the checked-out version. * The response of `SetProperties` and `CheckIn` has the new latest version in `object_info.latest_version` (version information and properties). ## Code The recipe creates a test document, sets Keywords (1004) in one call (A), then checks the document out, sets Keywords and Document date (1002) in one `SetProperties` call, and checks it in (B). If anything fails while the document is checked out, it undoes the check-out. At the end it deletes the document, unless you pass `--keep`. ### Python ```python # One call: the server checks the object out, sets the values and checks it in. # Name the version you read; with fail_if_newer_version_exists the call fails instead # of overwriting a change someone else made after that version. updated = objects.SetProperties(ops.SetPropertiesRequest( obj_ver=obj_ver(object_id.type, object_id.item_id.internal_id, version), allow_modifying_checked_in_object=True, fail_if_newer_version_exists=True, # Other properties stay as they are (remove_unspecified_properties is false). properties=[property_value(1004, text("grpc, sample"))])) # Keywords version = updated.object_info.latest_version.object_version.version_info \ .version.internal_version print(f"Set Keywords: now version {version}") ``` *Source: [python/recipes/update_properties.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/update_properties.py)* ```python def check_out_set_check_in(objects, object_id) -> None: """Check out, set, check in: several changes become one new version, and nobody else can change the object meanwhile.""" checked_out = objects.CheckOut(ops.CheckOutRequest(obj_id=object_id)) working_version = checked_out.object_info.object_version.version_info.version print(f"Checked out: working version {working_version.internal_version}") working = obj_ver(object_id.type, object_id.item_id.internal_id, working_version.internal_version) try: objects.SetProperties(ops.SetPropertiesRequest( obj_ver=working, properties=[ property_value(1004, text("grpc, sample, checked out")), # Keywords property_value(1002, date(datetime.now(timezone.utc).date())), # Document date ])) checked_in = objects.CheckIn(ops.CheckInRequest(obj_ver=working)) except Exception: # Do not leave the object checked out. objects.UndoCheckout(ops.UndoCheckoutRequest(obj_ver=working)) raise version = checked_in.object_info.latest_version.object_version.version_info.version print(f"Checked in: now version {version.internal_version}") ``` *Source: [python/recipes/update_properties.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/update_properties.py)* ### C# ```csharp // One call: the server checks the object out, sets the values and checks it in. // Name the version you read; with fail_if_newer_version_exists the call fails instead // of overwriting a change someone else made after that version. var updated = await objects.SetPropertiesAsync(new SetPropertiesRequest { ObjVer = ObjVer(objId.Type, objId.ItemId.InternalId, version), AllowModifyingCheckedInObject = true, FailIfNewerVersionExists = true, // Other properties stay as they are (remove_unspecified_properties is false). Properties = { Property(1004, Text("grpc, sample")) }, // Keywords }); version = updated.ObjectInfo.LatestVersion.ObjectVersion.VersionInfo.Version.InternalVersion; Console.WriteLine($"Set Keywords: now version {version}"); ``` *Source: [csharp/Recipes/UpdateProperties.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/UpdateProperties.cs)* ```csharp // Check out, set, check in: several changes become one new version, and nobody else // can change the object meanwhile. var checkedOut = await objects.CheckOutAsync(new CheckOutRequest { ObjId = objId }); var workingVersion = checkedOut.ObjectInfo.ObjectVersion.VersionInfo.Version; Console.WriteLine($"Checked out: working version {workingVersion.InternalVersion}"); try { var working = ObjVer(objId.Type, objId.ItemId.InternalId, workingVersion.InternalVersion); await objects.SetPropertiesAsync(new SetPropertiesRequest { ObjVer = working, Properties = { Property(1004, Text("grpc, sample, checked out")), // Keywords Property(1002, Date(DateOnly.FromDateTime(DateTime.UtcNow))), // Document date }, }); var checkedIn = await objects.CheckInAsync(new CheckInRequest { ObjVer = working }); version = checkedIn.ObjectInfo.LatestVersion.ObjectVersion.VersionInfo.Version.InternalVersion; Console.WriteLine($"Checked in: now version {version}"); } catch { // Do not leave the object checked out. await objects.UndoCheckoutAsync(new UndoCheckoutRequest { ObjVer = ObjVer(objId.Type, objId.ItemId.InternalId, workingVersion.InternalVersion), }); throw; } ``` *Source: [csharp/Recipes/UpdateProperties.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/UpdateProperties.cs)* ### Go ```go // One call: the server checks the object out, sets the values and checks it in. // Name the version you read; with fail_if_newer_version_exists the call fails instead of // overwriting a change someone else made after that version. updated, err := objects.SetProperties(ctx, &mfiles.SetPropertiesRequest{ ObjVer: mfilesclient.ObjVer(0, id, version), AllowModifyingCheckedInObject: true, FailIfNewerVersionExists: true, // Other properties stay as they are (remove_unspecified_properties is false). Properties: []*mfiles.PropertyValue{ mfilesclient.PropertyValue(1004, mfilesclient.Text("grpc, sample")), // Keywords }, }) if err != nil { return err } version = updated.GetObjectInfo().GetLatestVersion().GetObjectVersion().GetVersionInfo(). GetVersion().GetInternalVersion() fmt.Printf("Set Keywords: now version %d\n", version) ``` *Source: [go/recipes/update_properties/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/update_properties/main.go)* ```go // checkOutSetCheckIn makes several changes into one new version; nobody else can change the // object meanwhile. func checkOutSetCheckIn(ctx context.Context, objects mfiles.IRPCObjectOperationsClient, objID *mfiles.ObjID) error { checkedOut, err := objects.CheckOut(ctx, &mfiles.CheckOutRequest{ObjId: objID}) if err != nil { return err } workingVersion := checkedOut.GetObjectInfo().GetObjectVersion().GetVersionInfo().GetVersion() fmt.Printf("Checked out: working version %d\n", workingVersion.GetInternalVersion()) working := mfilesclient.ObjVer(objID.GetType(), objID.GetItemId().GetInternalId(), workingVersion.GetInternalVersion()) today := time.Now().UTC() _, err = objects.SetProperties(ctx, &mfiles.SetPropertiesRequest{ ObjVer: working, Properties: []*mfiles.PropertyValue{ mfilesclient.PropertyValue(1004, mfilesclient.Text("grpc, sample, checked out")), // Keywords mfilesclient.PropertyValue(1002, // Document date mfilesclient.Date(today.Year(), today.Month(), today.Day())), }, }) var checkedIn *mfiles.CheckInResponse if err == nil { checkedIn, err = objects.CheckIn(ctx, &mfiles.CheckInRequest{ObjVer: working}) } if err != nil { // Do not leave the object checked out. _, _ = objects.UndoCheckout(context.WithoutCancel(ctx), &mfiles.UndoCheckoutRequest{ObjVer: working}) return err } fmt.Printf("Checked in: now version %d\n", checkedIn.GetObjectInfo().GetLatestVersion(). GetObjectVersion().GetVersionInfo().GetVersion().GetInternalVersion()) return nil } ``` *Source: [go/recipes/update_properties/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/update_properties/main.go)* Output: ```text Created [docs-test] Python update-properties (ID 612, version 1, modified 2026-10-31 14:55:57 UTC) Set Keywords: now version 2 Checked out: working version 3 Checked in: now version 3 Property 1002: 2026-10-31 Property 1004: grpc, sample, checked out Deleted object 0-612 ``` The checked-out version keeps its number when it is checked in: the changes made while the document was checked out became one version, 3. ## Pitfalls * **`remove_unspecified_properties` true removes every property you did not send.** Leave it false unless you send the complete set. * **A stale write fails** with `0x800400BB` "There is a newer version of this object on the server." when `fail_if_newer_version_exists` is true. Read the object again, reapply your change and retry. With the flag false, the same call succeeds and writes over the newer version's values. * **A checked-in object** needs `allow_modifying_checked_in_object` true; without it the call fails with `0x800400B2` "The object is not checked out." * **Comment (33) belongs to a version.** A comment set in one change is not carried to the next version. * Several `SetProperties` calls on a checked-out object, then `CheckIn`, make one version, not one per call. * If your program stops between `CheckOut` and `CheckIn`, the object stays checked out to you. Check it in, or undo the check-out with [UndoCheckout](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/UndoCheckout/index.md). ## See also * [Objects and versions](https://developer.m-files.com/APIs/gRPC-API/concepts/objects-and-versions/index.md): versions and check-out. * [Properties and values](https://developer.m-files.com/APIs/gRPC-API/concepts/properties-and-values/index.md): how to build each kind of value. * [Change the workflow state](https://developer.m-files.com/APIs/gRPC-API/guides/workflow-state/index.md), which is also a property change. --- # Change the workflow state Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/workflow-state/ An object's workflow and state are property values: Workflow (38) and State (39). Read which states and transitions the workflow has and which of them the user may take, then change the state with a property update. ## Calls * [GetWorkflowStatesAndTransitions](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/GetWorkflowStatesAndTransitions/index.md) with `workflow_id`. Add `current_state` (the object's State value) and `obj_ver` to get the transitions and the user's rights for that object: * `states`: `id`, `name`, `can_current_user_select`. * `transitions`: `id`, `name`, `from_state_id`, `to_state_id`, `can_current_user_select`. * [SetProperties](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCObjectOperations/SetProperties/index.md) with: * 38 Workflow: a lookup to value list 7, item = workflow ID (to put an object into a workflow); * 39 State: a lookup to value list 8, item = state ID. A class can have a default workflow (`default_workflow` of the [ObjectClass](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.ObjectClass/index.md)); see [Read the vault structure](https://developer.m-files.com/APIs/gRPC-API/guides/vault-structure/index.md). ## Code The recipe creates a test document that is already in a workflow: it sets Workflow (38) and State (39) when it creates the document. It then reads the workflow's states and transitions for that document and sets State to the target state. The defaults are workflow 107 ("Contract Approval Workflow" in the M-Files Sample Vault), initial state 137 ("Draft") and target state 141 ("Rejected"); pass others as arguments. At the end it deletes the document, unless you pass `--keep`. ### Python ```python # The states and transitions of the workflow, with what this user may select for # this object in its current state. version = obj_ver(object_id.type, object_id.item_id.internal_id, created.version_info.version.internal_version) workflow = objects.GetWorkflowStatesAndTransitions( ops.GetWorkflowStatesAndTransitionsRequest( workflow_id=workflow_id, current_state=lookup(8, initial_state), obj_ver=version)) for state in workflow.states: print(f" State {state.id} {state.name}: " f"selectable {state.can_current_user_select}") for transition in workflow.transitions: print(f" Transition {transition.id} {transition.name}: " f"{transition.from_state_id} -> {transition.to_state_id}, " f"selectable {transition.can_current_user_select}") # Change the state by setting property 39 "Workflow State" on the version just read. updated = objects.SetProperties(ops.SetPropertiesRequest( obj_ver=version, allow_modifying_checked_in_object=True, fail_if_newer_version_exists=True, properties=[property_value(39, lookup(8, target_state))])) ``` *Source: [python/recipes/workflow_state.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/workflow_state.py)* ### C# ```csharp // The states and transitions of the workflow, with what this user may select for // this object in its current state. var objVer = ObjVer(objId.Type, objId.ItemId.InternalId, created.VersionInfo.Version.InternalVersion); var workflow = await objects.GetWorkflowStatesAndTransitionsAsync(new GetWorkflowStatesAndTransitionsRequest { WorkflowId = workflowId, CurrentState = Lookup(8, initialState), ObjVer = objVer, }); foreach (var state in workflow.States) Console.WriteLine($" State {state.Id} {state.Name}: selectable {state.CanCurrentUserSelect}"); foreach (var transition in workflow.Transitions) { Console.WriteLine($" Transition {transition.Id} {transition.Name}: {transition.FromStateId} -> " + $"{transition.ToStateId}, selectable {transition.CanCurrentUserSelect}"); } // Change the state by setting property 39 "Workflow State" on the version just read. var updated = await objects.SetPropertiesAsync(new SetPropertiesRequest { ObjVer = objVer, AllowModifyingCheckedInObject = true, FailIfNewerVersionExists = true, Properties = { Property(39, Lookup(8, targetState)) }, }); ``` *Source: [csharp/Recipes/WorkflowState.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/WorkflowState.cs)* ### Go ```go // The states and transitions of the workflow, with what this user may select for this // object in its current state. objVer := mfilesclient.ObjVer(objID.GetType(), objID.GetItemId().GetInternalId(), created.GetVersionInfo().GetVersion().GetInternalVersion()) workflow, err := objects.GetWorkflowStatesAndTransitions(ctx, &mfiles.GetWorkflowStatesAndTransitionsRequest{ WorkflowId: workflowID, CurrentState: mfilesclient.Lookup(8, initialState), ObjVer: objVer, }) if err != nil { return err } for _, state := range workflow.GetStates() { fmt.Printf(" State %d %s: selectable %v\n", state.GetId(), state.GetName(), state.GetCanCurrentUserSelect()) } for _, transition := range workflow.GetTransitions() { fmt.Printf(" Transition %d %s: %d -> %d, selectable %v\n", transition.GetId(), transition.GetName(), transition.GetFromStateId(), transition.GetToStateId(), transition.GetCanCurrentUserSelect()) } // Change the state by setting property 39 "Workflow State" on the version just read. updated, err := objects.SetProperties(ctx, &mfiles.SetPropertiesRequest{ ObjVer: objVer, AllowModifyingCheckedInObject: true, FailIfNewerVersionExists: true, Properties: []*mfiles.PropertyValue{ mfilesclient.PropertyValue(39, mfilesclient.Lookup(8, targetState)), }, }) if err != nil { return err } ``` *Source: [go/recipes/workflow_state/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/workflow_state/main.go)* Output against the Sample Vault: ```text Created [docs-test] Python workflow-state (ID 613, version 1, modified 2026-10-31 14:55:59 UTC) State 137 Draft: selectable True State 138 Waiting for Approval: selectable True State 139 Approved: selectable False State 140 Expired: selectable False State 141 Rejected: selectable True Transition 5 Rejected: 137 -> 141, selectable True Transition 11 Waiting for Approval: 137 -> 138, selectable True State is now Rejected (ID 141), version 2 Deleted object 0-613 ``` ## Pitfalls * **Transitions need the object.** Without `current_state` and `obj_ver`, the response lists the states but no transitions, and every state has `can_current_user_select` true because the rights cannot be checked. * **Selectable does not mean allowed.** A state can have preconditions that `can_current_user_select` does not check. In the Sample Vault, state 138 ("Waiting for Approval") requires the Owner property; moving the test document there fails with `0x80040840` "The preconditions specified for the new state are not satisfied.", although the transition 137 -> 138 is selectable. That is why the recipe's default target is 141. * **A state can take your rights away.** States can change the object's permissions. In the Sample Vault, moving a new document into the first state of workflow 102 ("Received, awaiting checking") succeeds, but the response has `object_info.is_visible` false and an empty `latest_version`: the user can no longer see the document, and deleting it fails with "Access denied" (`0x800407E4`). Check `is_visible` and `latest_version.current_user_permissions` in the response. * **State IDs and transition IDs are separate ID spaces**, and both differ between vaults. * Each state change creates a new object version (version 2 above). ## See also * [Workflows](https://developer.m-files.com/APIs/gRPC-API/concepts/workflows/index.md): states, transitions and electronic signatures. * [Update properties safely](https://developer.m-files.com/APIs/gRPC-API/guides/update-properties/index.md) --- # Page through large result sets Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/paging/ One search call returns at most `limit` results. To go through all matching objects, sort the results by object ID and ask for the next page with an extra condition "object ID greater than the last ID so far" (keyset paging). Each page is an independent search, so objects added or removed between pages do not shift the pages. ## Calls [SearchObjects](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/SearchObjects/index.md), repeated, with: | Field | Value | |---|---| | `conditions` | Your conditions, plus `status_value` `STATUS_TYPE_OBJECT_ID`, `CONDITION_TYPE_GREATER_THAN`, integer = the last object ID of the previous page (0 for the first page) | | `sorting` | One [SortRule](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.SortRule/index.md) with `key` -102: object ID, ascending | | `limit` | The page size | Stop when a page is empty or `has_more_results` is false. ## Code The builders (`is_object_type`, `status_condition`, `integer`, ...) are those of the [search recipe](https://developer.m-files.com/APIs/gRPC-API/guides/search/index.md). ### Python ```python # Keyset paging: sort by object ID and ask each page for IDs above the last one seen. last_id, page, total = 0, 0, 0 while True: response = search.SearchObjects(IRPCSearch_pb2.SearchObjectsRequest( conditions=[structs.SearchConditionArray(value=[ is_object_type(0), is_not_deleted(), status_condition(enums.STATUS_TYPE_OBJECT_ID, enums.CONDITION_TYPE_GREATER_THAN, integer(last_id)), ])], sorting=structs.SearchResultsSorting(rules=structs.SortRuleValueArray( values=[structs.SortRule(key=SORT_BY_OBJECT_ID)])), limit=page_size)) if not response.results: break page += 1 total += len(response.results) first_id = response.results[0].object.object_info.obj_id.item_id.internal_id last_id = response.results[-1].object.object_info.obj_id.item_id.internal_id print(f"Page {page}: {len(response.results)} documents, IDs {first_id}-{last_id}") if not response.has_more_results: break ``` *Source: [python/recipes/paging.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/paging.py)* ### C# ```csharp // Keyset paging: sort by object ID and ask each page for IDs above the last one seen. int lastId = 0, page = 0, total = 0; while (true) { var request = new SearchObjectsRequest { Conditions = { new SearchConditionArray { Value = { IsObjectType(0), IsNotDeleted(), StatusCondition(StatusType.ObjectId, ConditionType.GreaterThan, Integer(lastId)), }, }, }, Sorting = new SearchResultsSorting { Rules = new SortRuleValueArray { Values = { new SortRule { Key = SortByObjectId } } }, }, Limit = (uint)pageSize, }; var response = await search.SearchObjectsAsync(request); if (response.Results.Count == 0) break; page++; total += response.Results.Count; int firstId = response.Results[0].Object.ObjectInfo.ObjId.ItemId.InternalId; lastId = response.Results[^1].Object.ObjectInfo.ObjId.ItemId.InternalId; Console.WriteLine($"Page {page}: {response.Results.Count} documents, IDs {firstId}-{lastId}"); if (!response.HasMoreResults) break; } ``` *Source: [csharp/Recipes/Paging.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/Paging.cs)* ### Go ```go // Keyset paging: sort by object ID and ask each page for IDs above the last one seen. var lastID, page, total int32 for { response, err := search.SearchObjects(ctx, &mfiles.SearchObjectsRequest{ Conditions: mfilesclient.AllOf( mfilesclient.IsObjectType(0), mfilesclient.IsNotDeleted(), mfilesclient.StatusCondition(mfiles.StatusType_STATUS_TYPE_OBJECT_ID, mfiles.ConditionType_CONDITION_TYPE_GREATER_THAN, mfilesclient.Integer(lastID)), ), Sorting: mfilesclient.SortBy(sortByObjectID, false), Limit: uint32(pageSize), }) if err != nil { return err } results := response.GetResults() if len(results) == 0 { break } page++ total += int32(len(results)) firstID := objectID(results[0]) lastID = objectID(results[len(results)-1]) fmt.Printf("Page %d: %d documents, IDs %d-%d\n", page, len(results), firstID, lastID) if !response.GetHasMoreResults() { break } } ``` *Source: [go/recipes/paging/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/paging/main.go)* Output against the Sample Vault: ```text Page 1: 100 documents, IDs 23-236 Page 2: 100 documents, IDs 237-414 Page 3: 26 documents, IDs 415-445 Total: 226 documents in 3 pages ``` ## Pitfalls * **Sort key -102 sorts by object ID ascending only.** `sort_descending` with it fails with "The content can't be sorted across all pages." (`E_MFILES_SORTING_NOT_SUPPORTED`). It also cannot be combined with a full-text (any field) condition or with `SearchFlags.look_in_all_versions`. * **Page by object ID within one object type.** Object IDs are unique only within an object type; this recipe searches documents only. * `limit` 0 means no limit: on a large vault, one very large response. Page instead. * [GetObjectCountInSearch](https://developer.m-files.com/APIs/gRPC-API/Reference/MFiles.IRPCSearch/GetObjectCountInSearch/index.md) counts the matches without returning them, if you only need the number. ## See also * [Search](https://developer.m-files.com/APIs/gRPC-API/concepts/search/index.md): results, limits and sorting. * [Search for documents](https://developer.m-files.com/APIs/gRPC-API/guides/search/index.md) --- # Handle errors Location: [gRPC API](https://developer.m-files.com/APIs/gRPC-API/index.md) > [Guides](https://developer.m-files.com/APIs/gRPC-API/index.md#guides) HTML version: https://developer.m-files.com/APIs/gRPC-API/guides/errors/ A failed call ends with a gRPC status. For M-Files errors the status is `UNKNOWN`, and the M-Files error code (an HRESULT) and the error stack travel in trailers. Decode them, act on the codes you handle, and report the rest. ## How errors arrive | What | Where | |---|---| | gRPC status | `UNKNOWN` for M-Files errors; `UNAUTHENTICATED` for a missing or expired session (no trailers); `UNAVAILABLE` and others from the transport | | Message | The status message: the message of the top-level error | | Error stack | Trailer `mfiles-error-serialized-bin`: a serialized [RPCErrorInfoArray](https://developer.m-files.com/APIs/gRPC-API/Reference/Messages/MFiles.RPCErrorInfoArray/index.md), ordered from the original cause (first) to the top-level error (last) | | Diagnostic text | Trailer `mfiles-error-text-bin`: the stack as UTF-8 text, for logs | `error_code` is a signed 32-bit integer; mask it with `0xFFFFFFFF` to compare it with codes written in hex. Details and the table of common codes: [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md). ## What the helpers give you The samples' helpers turn a failed call into an error object with the same parts in every language: | Part | Python (`MFilesError`) | C# (`MFilesException`) | Go (`*mfilesclient.Error`) | |---|---|---|---| | Error code | `hresult`, `hresult_hex`, `hresult_signed` | `ErrorCode`, `ErrorCodeHex` | `HResult`, `SignedHResult()` | | All codes, cause first | `codes` | `ErrorCodes` | `Codes` | | Code anywhere in the stack | `has(code)` | `Has(code)` | `Has(code)` | | Message | `message` | `Message` | `Message` | | gRPC status | `status` | `StatusCode` | `Status` | **The error code** is the code of the last entry of the stack, the top-level error that the status message describes. If that entry has the generic code `0x80040001`, which only passes an error on, the code is that of the last entry that has another code. Without an error stack (for example `UNAUTHENTICATED`) there is no error code. **Test with "has".** The code you handle can be anywhere in the stack, so test with `has` rather than comparing the error code. Decoding, in Python: ```python self.long_message: str = trailers.get(ERROR_TEXT_KEY, b"").decode("utf-8", "replace") self.stack = list(structs_pb2.RPCErrorInfoArray.FromString( trailers.get(ERROR_STACK_KEY, b"")).value) self.codes = [entry.error_code & 0xFFFFFFFF for entry in self.stack] # cause first # The top-level error, skipping entries that only pass the error on. self.hresult: Optional[int] = next( (code for code in reversed(self.codes) if code != E_PASS_THROUGH), self.codes[-1] if self.codes else None) ``` *Source: [python/mfiles_client.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/mfiles_client.py)* ## Code The recipe asks for an object that does not exist, then calls a method that needs vault administrator rights. ### Python ```python # Not found: a document ID that does not exist. objects = client.stub(IRPCObjectOperations_pb2_grpc.IRPCObjectOperationsStub) try: objects.GetObjectInfoAndProperties( IRPCObjectOperations_pb2.GetObjectInfoAndPropertiesRequest( obj_id=obj_id(0, 999_999_999), latest_version=True)) raise RuntimeError("Expected the call to fail.") except MFilesError as error: if not error.has(E_NOT_FOUND): raise print_error("Not found", error) # Access denied: an administrative call by a user who is not a vault administrator. admin = client.stub(IRPCPropertyDefsAdmin_pb2_grpc.IRPCPropertyDefsAdminStub) try: admin.GetPropertyDefsAdmin(IRPCPropertyDefsAdmin_pb2.GetPropertyDefsAdminRequest()) print("Access denied: not provoked, the user is a vault administrator") except MFilesError as error: if not error.has(E_ACCESS_DENIED): raise print_error("Access denied", error) ``` *Source: [python/recipes/errors.py](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/python/recipes/errors.py)* ### C# ```csharp // Not found: a document ID that does not exist. var objects = new IRPCObjectOperations.IRPCObjectOperationsClient(connection.CallInvoker); try { await objects.GetObjectInfoAndPropertiesAsync(new GetObjectInfoAndPropertiesRequest { ObjId = ObjId(0, 999_999_999), LatestVersion = true, }); throw new InvalidOperationException("Expected the call to fail."); } catch (MFilesException e) when (e.Has(NotFound)) { Print("Not found", e); } // Access denied: an administrative call by a user who is not a vault administrator. var admin = new IRPCPropertyDefsAdmin.IRPCPropertyDefsAdminClient(connection.CallInvoker); try { await admin.GetPropertyDefsAdminAsync(new GetPropertyDefsAdminRequest()); Console.WriteLine("Access denied: not provoked, the user is a vault administrator"); } catch (MFilesException e) when (e.Has(AccessDenied)) { Print("Access denied", e); } ``` *Source: [csharp/Recipes/Errors.cs](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/csharp/Recipes/Errors.cs)* ### Go ```go // Not found: a document ID that does not exist. objects := mfiles.NewIRPCObjectOperationsClient(client.Conn) _, err = objects.GetObjectInfoAndProperties(ctx, &mfiles.GetObjectInfoAndPropertiesRequest{ ObjId: mfilesclient.ObjID(0, 999_999_999), LatestVersion: true, }) if mfilesErr, ok := mfilesclient.AsError(err); ok && mfilesErr.Has(mfilesclient.CodeNotFound) { printError("Not found", mfilesErr) } else { return fmt.Errorf("expected a not-found error, got %v", err) } // Access denied: an administrative call by a user who is not a vault administrator. admin := mfiles.NewIRPCPropertyDefsAdminClient(client.Conn) _, err = admin.GetPropertyDefsAdmin(ctx, &mfiles.GetPropertyDefsAdminRequest{}) switch mfilesErr, ok := mfilesclient.AsError(err); { case err == nil: fmt.Println("Access denied: not provoked, the user is a vault administrator") case ok && mfilesErr.Has(mfilesclient.CodeAccessDenied): printError("Access denied", mfilesErr) default: return err } ``` *Source: [go/recipes/errors/main.go](https://developer.m-files.com/APIs/gRPC-API/downloads/samples/go/recipes/errors/main.go)* Output for a normal user (the stack lists each code once): ```text Not found: gRPC status UNKNOWN, M-Files error 0x8004000B (-2147221493) Message: Not found. Error stack, original error first: 0x8004000B Access denied: gRPC status UNKNOWN, M-Files error 0x800407E4 (-2147219484) Message: Access denied. You do not have the access rights required by this operation. Error stack, original error first: 0x800400A9, 0x800407E4 ``` The full access-denied stack is `0x800400A9` followed by six entries of `0x800407E4`; the not-found stack is eight entries of `0x8004000B`. ## Pitfalls * **"Not found" also means "not visible".** An object the user has no right to see fails with `0x8004000B`, exactly like one that does not exist. * **The first and last entries can differ.** For access denied, the error code is `0x800407E4` and the first entry says what was missing: `0x800400A9` (a vault-level right) or `0x800400AA` (rights to the object). Test with `has`, not only with the error code. * **`UNAUTHENTICATED` has no M-Files code** and no error stack. Log in again and retry; see [Authentication and sessions](https://developer.m-files.com/APIs/gRPC-API/runtime/authentication/index.md). * **Messages contain `\r\n`** and can change between versions: match codes, not text. * The diagnostic text and the `file` and `line` of each entry name server source files; log them, do not show them to end users. ## See also * [Errors](https://developer.m-files.com/APIs/gRPC-API/runtime/errors/index.md): the full error model and the common error codes. * [Permissions](https://developer.m-files.com/APIs/gRPC-API/concepts/permissions/index.md): what access denied looks like for objects.